tmux-agent 0.1.0

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 (161) hide show
  1. package/.codex/skills/speckit/SKILL.md +173 -0
  2. package/.codex/skills/speckit/assets/templates/checklist-template.md +49 -0
  3. package/.codex/skills/speckit/assets/templates/notes-entrypoints-template.md +11 -0
  4. package/.codex/skills/speckit/assets/templates/notes-questions-template.md +7 -0
  5. package/.codex/skills/speckit/assets/templates/notes-readme-template.md +36 -0
  6. package/.codex/skills/speckit/assets/templates/notes-session-template.md +21 -0
  7. package/.codex/skills/speckit/assets/templates/plan-template.md +126 -0
  8. package/.codex/skills/speckit/assets/templates/spec-template.md +135 -0
  9. package/.codex/skills/speckit/assets/templates/tasks-template.md +269 -0
  10. package/.codex/skills/speckit/references/acceptance.md +183 -0
  11. package/.codex/skills/speckit/references/analyze.md +186 -0
  12. package/.codex/skills/speckit/references/checklist.md +302 -0
  13. package/.codex/skills/speckit/references/clarify-auto.md +69 -0
  14. package/.codex/skills/speckit/references/clarify-detailed.md +78 -0
  15. package/.codex/skills/speckit/references/clarify.md +189 -0
  16. package/.codex/skills/speckit/references/constitution.md +90 -0
  17. package/.codex/skills/speckit/references/group.md +89 -0
  18. package/.codex/skills/speckit/references/implement-task.md +115 -0
  19. package/.codex/skills/speckit/references/implement.md +129 -0
  20. package/.codex/skills/speckit/references/notes.md +82 -0
  21. package/.codex/skills/speckit/references/plan-deep.md +87 -0
  22. package/.codex/skills/speckit/references/plan-from-questions.md +115 -0
  23. package/.codex/skills/speckit/references/plan-from-review.md +89 -0
  24. package/.codex/skills/speckit/references/plan.md +97 -0
  25. package/.codex/skills/speckit/references/review-plan.md +156 -0
  26. package/.codex/skills/speckit/references/specify.md +246 -0
  27. package/.codex/skills/speckit/references/tasks.md +155 -0
  28. package/.codex/skills/speckit/references/taskstoissues.md +33 -0
  29. package/.codex/skills/speckit/scripts/bash/check-prerequisites.sh +206 -0
  30. package/.codex/skills/speckit/scripts/bash/common.sh +191 -0
  31. package/.codex/skills/speckit/scripts/bash/create-new-feature.sh +259 -0
  32. package/.codex/skills/speckit/scripts/bash/extract-coded-points.sh +322 -0
  33. package/.codex/skills/speckit/scripts/bash/extract-spec-ids.sh +238 -0
  34. package/.codex/skills/speckit/scripts/bash/extract-tasks.sh +295 -0
  35. package/.codex/skills/speckit/scripts/bash/extract-user-stories.sh +312 -0
  36. package/.codex/skills/speckit/scripts/bash/setup-notes.sh +182 -0
  37. package/.codex/skills/speckit/scripts/bash/setup-plan.sh +110 -0
  38. package/.codex/skills/speckit/scripts/bash/show-todo-tasks.sh +257 -0
  39. package/.codex/skills/speckit/scripts/bash/spec-group-checklist.sh +402 -0
  40. package/.codex/skills/speckit/scripts/bash/spec-group-members.sh +215 -0
  41. package/.codex/skills/speckit/scripts/bash/spec-registry-graph.sh +399 -0
  42. package/.specify/memory/constitution.md +67 -0
  43. package/.specify/templates/agent-file-template.md +28 -0
  44. package/.specify/templates/checklist-template.md +49 -0
  45. package/.specify/templates/plan-template.md +126 -0
  46. package/.specify/templates/spec-template.md +135 -0
  47. package/.specify/templates/tasks-template.md +269 -0
  48. package/README.md +128 -0
  49. package/README.zh-CN.md +127 -0
  50. package/bun.lock +269 -0
  51. package/dist/cli/commands/codex/forkHome.js +88 -0
  52. package/dist/cli/commands/codex/send.js +55 -0
  53. package/dist/cli/commands/codex/sessionInfo.js +42 -0
  54. package/dist/cli/commands/codex/spawn.js +68 -0
  55. package/dist/cli/commands/find.js +26 -0
  56. package/dist/cli/commands/paneKill.js +33 -0
  57. package/dist/cli/commands/paneSpawn.js +40 -0
  58. package/dist/cli/commands/paneTitle.js +33 -0
  59. package/dist/cli/commands/read.js +34 -0
  60. package/dist/cli/commands/send.js +51 -0
  61. package/dist/cli/commands/snapshot.js +19 -0
  62. package/dist/cli/commands/ui/select.js +41 -0
  63. package/dist/cli/commands/windowKill.js +25 -0
  64. package/dist/cli/commands/windowLs.js +15 -0
  65. package/dist/cli/commands/windowNew.js +28 -0
  66. package/dist/cli/commands/windowRename.js +25 -0
  67. package/dist/cli/index.js +365 -0
  68. package/dist/cli/parse.js +39 -0
  69. package/dist/lib/codex/forkHome.js +101 -0
  70. package/dist/lib/codex/isCodexPane.js +55 -0
  71. package/dist/lib/codex/send.js +58 -0
  72. package/dist/lib/codex/sessionInfo.js +449 -0
  73. package/dist/lib/codex/spawn.js +246 -0
  74. package/dist/lib/contracts/types.js +2 -0
  75. package/dist/lib/fs/safeRm.js +32 -0
  76. package/dist/lib/io/readStdin.js +14 -0
  77. package/dist/lib/os/process.js +55 -0
  78. package/dist/lib/output/format.js +95 -0
  79. package/dist/lib/proc/lsof.js +42 -0
  80. package/dist/lib/proc/ps.js +60 -0
  81. package/dist/lib/targeting/errors.js +13 -0
  82. package/dist/lib/targeting/resolvePaneTarget.js +91 -0
  83. package/dist/lib/targeting/resolveWindowTarget.js +40 -0
  84. package/dist/lib/targeting/scope.js +58 -0
  85. package/dist/lib/tmux/capturePane.js +20 -0
  86. package/dist/lib/tmux/exec.js +66 -0
  87. package/dist/lib/tmux/paneOps.js +29 -0
  88. package/dist/lib/tmux/paste.js +23 -0
  89. package/dist/lib/tmux/sendKeys.js +47 -0
  90. package/dist/lib/tmux/session.js +29 -0
  91. package/dist/lib/tmux/snapshotPanes.js +46 -0
  92. package/dist/lib/tmux/snapshotWindows.js +24 -0
  93. package/dist/lib/tmux/windowOps.js +32 -0
  94. package/dist/lib/ui/popupSelect.js +432 -0
  95. package/dist/lib/ui/popupSupport.js +76 -0
  96. package/package.json +23 -0
  97. package/src/cli/commands/codex/forkHome.ts +141 -0
  98. package/src/cli/commands/codex/send.ts +83 -0
  99. package/src/cli/commands/codex/sessionInfo.ts +59 -0
  100. package/src/cli/commands/codex/spawn.ts +90 -0
  101. package/src/cli/commands/find.ts +40 -0
  102. package/src/cli/commands/paneKill.ts +49 -0
  103. package/src/cli/commands/paneSpawn.ts +53 -0
  104. package/src/cli/commands/paneTitle.ts +50 -0
  105. package/src/cli/commands/read.ts +48 -0
  106. package/src/cli/commands/send.ts +71 -0
  107. package/src/cli/commands/snapshot.ts +28 -0
  108. package/src/cli/commands/ui/select.ts +49 -0
  109. package/src/cli/commands/windowKill.ts +35 -0
  110. package/src/cli/commands/windowLs.ts +20 -0
  111. package/src/cli/commands/windowNew.ts +40 -0
  112. package/src/cli/commands/windowRename.ts +36 -0
  113. package/src/cli/index.ts +430 -0
  114. package/src/lib/codex/forkHome.ts +148 -0
  115. package/src/lib/codex/isCodexPane.ts +56 -0
  116. package/src/lib/codex/send.ts +84 -0
  117. package/src/lib/codex/sessionInfo.ts +521 -0
  118. package/src/lib/codex/spawn.ts +305 -0
  119. package/src/lib/contracts/types.ts +30 -0
  120. package/src/lib/fs/safeRm.ts +32 -0
  121. package/src/lib/io/readStdin.ts +11 -0
  122. package/src/lib/output/format.ts +105 -0
  123. package/src/lib/proc/lsof.ts +44 -0
  124. package/src/lib/proc/ps.ts +70 -0
  125. package/src/lib/targeting/errors.ts +25 -0
  126. package/src/lib/targeting/resolvePaneTarget.ts +106 -0
  127. package/src/lib/targeting/resolveWindowTarget.ts +45 -0
  128. package/src/lib/targeting/scope.ts +76 -0
  129. package/src/lib/tmux/capturePane.ts +21 -0
  130. package/src/lib/tmux/exec.ts +90 -0
  131. package/src/lib/tmux/paneOps.ts +35 -0
  132. package/src/lib/tmux/paste.ts +20 -0
  133. package/src/lib/tmux/sendKeys.ts +72 -0
  134. package/src/lib/tmux/session.ts +27 -0
  135. package/src/lib/tmux/snapshotPanes.ts +52 -0
  136. package/src/lib/tmux/snapshotWindows.ts +23 -0
  137. package/src/lib/tmux/windowOps.ts +43 -0
  138. package/src/lib/ui/popupSelect.ts +561 -0
  139. package/src/lib/ui/popupSupport.ts +84 -0
  140. package/tests/e2e/codexForkHome.test.ts +146 -0
  141. package/tests/e2e/codexSessionInfo.test.ts +112 -0
  142. package/tests/e2e/codexTuiSend.test.ts +68 -0
  143. package/tests/integration/codexSpawn.test.ts +113 -0
  144. package/tests/integration/paneOps.test.ts +60 -0
  145. package/tests/integration/sendRead.test.ts +52 -0
  146. package/tests/integration/snapshot.test.ts +39 -0
  147. package/tests/integration/tmuxHarness.ts +39 -0
  148. package/tests/integration/windowOps.test.ts +60 -0
  149. package/tests/unit/codexSend.test.ts +105 -0
  150. package/tests/unit/codexSessionInfo.test.ts +88 -0
  151. package/tests/unit/codexSpawn.test.ts +34 -0
  152. package/tests/unit/keys.test.ts +30 -0
  153. package/tests/unit/outputFormat.test.ts +52 -0
  154. package/tests/unit/popupSelect.test.ts +77 -0
  155. package/tests/unit/popupSupport.test.ts +109 -0
  156. package/tests/unit/resolvePaneTarget.test.ts +43 -0
  157. package/tests/unit/resolveWindowTarget.test.ts +36 -0
  158. package/tests/unit/safeRm.test.ts +41 -0
  159. package/tests/unit/scope.test.ts +57 -0
  160. package/tsconfig.json +14 -0
  161. package/vitest.config.ts +16 -0
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ JSON_MODE=false
6
+ FORCE_OVERWRITE=false
7
+ DRY_RUN=false
8
+ FEATURE_OVERRIDE=""
9
+ POSITIONAL=()
10
+
11
+ while [[ $# -gt 0 ]]; do
12
+ case "$1" in
13
+ --json)
14
+ JSON_MODE=true
15
+ shift
16
+ ;;
17
+ --force)
18
+ FORCE_OVERWRITE=true
19
+ shift
20
+ ;;
21
+ --dry-run)
22
+ DRY_RUN=true
23
+ shift
24
+ ;;
25
+ --feature)
26
+ if [[ $# -lt 2 || "$2" == --* ]]; then
27
+ echo "Error: --feature requires a value" >&2
28
+ exit 1
29
+ fi
30
+ FEATURE_OVERRIDE="$2"
31
+ shift 2
32
+ ;;
33
+ --feature=*)
34
+ FEATURE_OVERRIDE="${1#--feature=}"
35
+ shift
36
+ ;;
37
+ --help|-h)
38
+ cat << 'EOF'
39
+ Usage: setup-notes.sh [--json] [--dry-run] [--force] [--feature <NNN|NNN-name>] [NNN|NNN-name]
40
+
41
+ Ensure `specs/<feature>/notes/` exists with minimal skeleton files (no overwrite by default).
42
+
43
+ OPTIONS:
44
+ --json Output results in JSON format
45
+ --dry-run Preview what would be created/overwritten without writing
46
+ --force Overwrite existing notes files from templates
47
+ --feature Target a specific spec (e.g., 025 or 025-my-feature)
48
+ --help Show this help message
49
+ EOF
50
+ exit 0
51
+ ;;
52
+ --*)
53
+ echo "Error: Unknown option '$1' (use --help for usage information)" >&2
54
+ exit 1
55
+ ;;
56
+ *)
57
+ POSITIONAL+=("$1")
58
+ shift
59
+ ;;
60
+ esac
61
+ done
62
+
63
+ if [[ -n "$FEATURE_OVERRIDE" ]]; then
64
+ SPECIFY_FEATURE="$FEATURE_OVERRIDE"
65
+ fi
66
+
67
+ if [[ ${#POSITIONAL[@]} -gt 0 ]]; then
68
+ if [[ -z "$FEATURE_OVERRIDE" && ${#POSITIONAL[@]} -eq 1 && "${POSITIONAL[0]}" =~ ^[0-9]{3}(-.+)?$ ]]; then
69
+ SPECIFY_FEATURE="${POSITIONAL[0]}"
70
+ else
71
+ echo "Error: Unknown argument(s): ${POSITIONAL[*]} (use --help for usage information)" >&2
72
+ exit 1
73
+ fi
74
+ fi
75
+
76
+ SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
77
+ SKILL_DIR="$(CDPATH="" cd "$SCRIPT_DIR/../.." && pwd)"
78
+ source "$SCRIPT_DIR/common.sh"
79
+
80
+ eval $(get_feature_paths)
81
+ check_feature_branch "$CURRENT_BRANCH" || exit 1
82
+
83
+ if [[ ! -d "$FEATURE_DIR" ]]; then
84
+ echo "ERROR: Feature directory not found: $FEATURE_DIR" >&2
85
+ echo 'Run $speckit specify first to create the feature structure.' >&2
86
+ exit 1
87
+ fi
88
+
89
+ NOTES_DIR="$FEATURE_DIR/notes"
90
+ SESSIONS_DIR="$NOTES_DIR/sessions"
91
+ NOTES_README="$NOTES_DIR/README.md"
92
+ NOTES_ENTRYPOINTS="$NOTES_DIR/entrypoints.md"
93
+ NOTES_QUESTIONS="$NOTES_DIR/questions.md"
94
+
95
+ TEMPLATE_README="$SKILL_DIR/assets/templates/notes-readme-template.md"
96
+ TEMPLATE_ENTRYPOINTS="$SKILL_DIR/assets/templates/notes-entrypoints-template.md"
97
+ TEMPLATE_QUESTIONS="$SKILL_DIR/assets/templates/notes-questions-template.md"
98
+
99
+ declare -a would_create=()
100
+ declare -a would_overwrite=()
101
+
102
+ if [[ ! -d "$NOTES_DIR" ]]; then
103
+ would_create+=("notes/")
104
+ fi
105
+ if [[ ! -d "$SESSIONS_DIR" ]]; then
106
+ would_create+=("notes/sessions/")
107
+ fi
108
+
109
+ if [[ ! -f "$NOTES_README" || ! -s "$NOTES_README" ]]; then
110
+ would_create+=("notes/README.md")
111
+ elif $FORCE_OVERWRITE; then
112
+ would_overwrite+=("notes/README.md")
113
+ fi
114
+
115
+ if [[ ! -f "$NOTES_ENTRYPOINTS" || ! -s "$NOTES_ENTRYPOINTS" ]]; then
116
+ would_create+=("notes/entrypoints.md")
117
+ elif $FORCE_OVERWRITE; then
118
+ would_overwrite+=("notes/entrypoints.md")
119
+ fi
120
+
121
+ if [[ ! -f "$NOTES_QUESTIONS" || ! -s "$NOTES_QUESTIONS" ]]; then
122
+ would_create+=("notes/questions.md")
123
+ elif $FORCE_OVERWRITE; then
124
+ would_overwrite+=("notes/questions.md")
125
+ fi
126
+
127
+ if ! $DRY_RUN; then
128
+ mkdir -p "$SESSIONS_DIR"
129
+
130
+ if $FORCE_OVERWRITE || [[ ! -s "$NOTES_README" ]]; then
131
+ if [[ -f "$TEMPLATE_README" ]]; then
132
+ cp "$TEMPLATE_README" "$NOTES_README"
133
+ else
134
+ [[ -f "$NOTES_README" ]] || touch "$NOTES_README"
135
+ fi
136
+ fi
137
+
138
+ if $FORCE_OVERWRITE || [[ ! -s "$NOTES_ENTRYPOINTS" ]]; then
139
+ if [[ -f "$TEMPLATE_ENTRYPOINTS" ]]; then
140
+ cp "$TEMPLATE_ENTRYPOINTS" "$NOTES_ENTRYPOINTS"
141
+ else
142
+ [[ -f "$NOTES_ENTRYPOINTS" ]] || touch "$NOTES_ENTRYPOINTS"
143
+ fi
144
+ fi
145
+
146
+ if $FORCE_OVERWRITE || [[ ! -s "$NOTES_QUESTIONS" ]]; then
147
+ if [[ -f "$TEMPLATE_QUESTIONS" ]]; then
148
+ cp "$TEMPLATE_QUESTIONS" "$NOTES_QUESTIONS"
149
+ else
150
+ [[ -f "$NOTES_QUESTIONS" ]] || touch "$NOTES_QUESTIONS"
151
+ fi
152
+ fi
153
+ fi
154
+
155
+ if $JSON_MODE; then
156
+ if [[ ${#would_create[@]} -eq 0 ]]; then
157
+ json_create="[]"
158
+ else
159
+ json_create=$(printf '"%s",' "${would_create[@]}")
160
+ json_create="[${json_create%,}]"
161
+ fi
162
+
163
+ if [[ ${#would_overwrite[@]} -eq 0 ]]; then
164
+ json_overwrite="[]"
165
+ else
166
+ json_overwrite=$(printf '"%s",' "${would_overwrite[@]}")
167
+ json_overwrite="[${json_overwrite%,}]"
168
+ fi
169
+
170
+ printf '{"FEATURE_DIR":"%s","NOTES_DIR":"%s","NOTES_README":"%s","NOTES_ENTRYPOINTS":"%s","NOTES_QUESTIONS":"%s","SESSIONS_DIR":"%s","WOULD_CREATE":%s,"WOULD_OVERWRITE":%s,"DRY_RUN":%s}\n' \
171
+ "$FEATURE_DIR" "$NOTES_DIR" "$NOTES_README" "$NOTES_ENTRYPOINTS" "$NOTES_QUESTIONS" "$SESSIONS_DIR" \
172
+ "$json_create" "$json_overwrite" "$DRY_RUN"
173
+ else
174
+ echo "FEATURE_DIR: $FEATURE_DIR"
175
+ echo "NOTES_DIR: $NOTES_DIR"
176
+ echo "NOTES_README: $NOTES_README"
177
+ echo "NOTES_ENTRYPOINTS: $NOTES_ENTRYPOINTS"
178
+ echo "NOTES_QUESTIONS: $NOTES_QUESTIONS"
179
+ echo "SESSIONS_DIR: $SESSIONS_DIR"
180
+ echo "DRY_RUN: $DRY_RUN"
181
+ fi
182
+
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ JSON_MODE=false
6
+ FORCE_OVERWRITE=false
7
+ FEATURE_OVERRIDE=""
8
+ POSITIONAL=()
9
+
10
+ while [[ $# -gt 0 ]]; do
11
+ case "$1" in
12
+ --json)
13
+ JSON_MODE=true
14
+ shift
15
+ ;;
16
+ --force)
17
+ FORCE_OVERWRITE=true
18
+ shift
19
+ ;;
20
+ --feature)
21
+ if [[ $# -lt 2 || "$2" == --* ]]; then
22
+ echo "Error: --feature requires a value" >&2
23
+ exit 1
24
+ fi
25
+ FEATURE_OVERRIDE="$2"
26
+ shift 2
27
+ ;;
28
+ --feature=*)
29
+ FEATURE_OVERRIDE="${1#--feature=}"
30
+ shift
31
+ ;;
32
+ --help|-h)
33
+ echo "Usage: $0 [--json] [--force] [--feature <NNN|NNN-name>] [NNN|NNN-name]"
34
+ echo " --json Output results in JSON format"
35
+ echo " --force Overwrite existing plan.md from template"
36
+ echo " --feature Target a specific spec (e.g., 025 or 025-my-feature)"
37
+ echo " --help Show this help message"
38
+ exit 0
39
+ ;;
40
+ --*)
41
+ echo "Error: Unknown option '$1' (use --help for usage information)" >&2
42
+ exit 1
43
+ ;;
44
+ *)
45
+ POSITIONAL+=("$1")
46
+ shift
47
+ ;;
48
+ esac
49
+ done
50
+
51
+ if [[ -n "$FEATURE_OVERRIDE" ]]; then
52
+ SPECIFY_FEATURE="$FEATURE_OVERRIDE"
53
+ fi
54
+
55
+ if [[ ${#POSITIONAL[@]} -gt 0 ]]; then
56
+ if [[ -z "$FEATURE_OVERRIDE" && ${#POSITIONAL[@]} -eq 1 && "${POSITIONAL[0]}" =~ ^[0-9]{3}(-.+)?$ ]]; then
57
+ SPECIFY_FEATURE="${POSITIONAL[0]}"
58
+ else
59
+ echo "Error: Unknown argument(s): ${POSITIONAL[*]} (use --help for usage information)" >&2
60
+ exit 1
61
+ fi
62
+ fi
63
+
64
+ # Get script directory and load common functions
65
+ SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
66
+ SKILL_DIR="$(CDPATH="" cd "$SCRIPT_DIR/../.." && pwd)"
67
+ source "$SCRIPT_DIR/common.sh"
68
+
69
+ # Get all paths and variables from common functions
70
+ eval $(get_feature_paths)
71
+
72
+ # Check if we have a valid feature id
73
+ check_feature_branch "$CURRENT_BRANCH" || exit 1
74
+
75
+ # Ensure the feature directory exists
76
+ mkdir -p "$FEATURE_DIR"
77
+
78
+ # Copy plan template if it exists (but do not overwrite by default)
79
+ TEMPLATE="$SKILL_DIR/assets/templates/plan-template.md"
80
+ if [[ -f "$TEMPLATE" ]]; then
81
+ if $FORCE_OVERWRITE || [[ ! -s "$IMPL_PLAN" ]]; then
82
+ cp "$TEMPLATE" "$IMPL_PLAN"
83
+ if ! $JSON_MODE; then
84
+ echo "Copied plan template to $IMPL_PLAN"
85
+ fi
86
+ else
87
+ if ! $JSON_MODE; then
88
+ echo "Plan already exists at $IMPL_PLAN (use --force to overwrite)"
89
+ fi
90
+ fi
91
+ else
92
+ if ! $JSON_MODE; then
93
+ echo "Warning: Plan template not found at $TEMPLATE"
94
+ fi
95
+ # Create a basic plan file if template doesn't exist
96
+ if [[ ! -f "$IMPL_PLAN" ]]; then
97
+ touch "$IMPL_PLAN"
98
+ fi
99
+ fi
100
+
101
+ # Output results
102
+ if $JSON_MODE; then
103
+ printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s"}\n' \
104
+ "$FEATURE_SPEC" "$IMPL_PLAN" "$FEATURE_DIR" "$CURRENT_BRANCH"
105
+ else
106
+ echo "FEATURE_SPEC: $FEATURE_SPEC"
107
+ echo "IMPL_PLAN: $IMPL_PLAN"
108
+ echo "SPECS_DIR: $FEATURE_DIR"
109
+ echo "BRANCH: $CURRENT_BRANCH"
110
+ fi
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Show pending (unchecked) tasks across specs, or for specific specs.
4
+ #
5
+ # Default (no feature args): summarize all specs that still have TODO tasks.
6
+ # With feature args: show all TODO tasks under the specified spec(s).
7
+ #
8
+ # This script is read-only and uses extract-tasks.sh for the detailed view.
9
+ #
10
+ # Usage:
11
+ # ./show-todo-tasks.sh [--json] [--feature <id>]... [<id>...]
12
+ #
13
+ # Examples:
14
+ # ./show-todo-tasks.sh
15
+ # ./show-todo-tasks.sh 024
16
+ # ./show-todo-tasks.sh --feature 024 --feature 025
17
+ # ./show-todo-tasks.sh --json --feature 024
18
+ #
19
+ # Notes:
20
+ # - Exit code matches extract-tasks.sh (e.g. duplicates => non-zero), but output is still printed.
21
+ # - Read-only.
22
+
23
+ set -euo pipefail
24
+
25
+ SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
26
+ EXTRACT="$SCRIPT_DIR/extract-tasks.sh"
27
+
28
+ JSON_MODE=false
29
+ SHOW_HELP=false
30
+ FORWARD_ARGS=()
31
+
32
+ while [[ $# -gt 0 ]]; do
33
+ case "$1" in
34
+ --json)
35
+ JSON_MODE=true
36
+ shift
37
+ ;;
38
+ --help|-h)
39
+ SHOW_HELP=true
40
+ shift
41
+ ;;
42
+ *)
43
+ FORWARD_ARGS+=("$1")
44
+ shift
45
+ ;;
46
+ esac
47
+ done
48
+
49
+ if $SHOW_HELP; then
50
+ cat << 'EOF'
51
+ Usage: show-todo-tasks.sh [--json] [--feature <id>]... [<id>...]
52
+
53
+ Default (no feature args): summarize all specs that still have TODO tasks.
54
+ With feature args: show pending tasks from tasks.md, with file+line evidence.
55
+
56
+ Options:
57
+ --json Output JSON (default: text)
58
+ --feature <id> Target a specific spec (repeatable)
59
+ --help, -h Show this help message
60
+
61
+ Examples:
62
+ ./show-todo-tasks.sh
63
+ ./show-todo-tasks.sh 024
64
+ ./show-todo-tasks.sh --feature 024 --feature 025
65
+ ./show-todo-tasks.sh --json --feature 024
66
+ EOF
67
+ exit 0
68
+ fi
69
+
70
+ if [[ ${#FORWARD_ARGS[@]} -eq 0 ]]; then
71
+ source "$SCRIPT_DIR/common.sh"
72
+ REPO_ROOT="$(get_repo_root)"
73
+ SPECS_DIR="$REPO_ROOT/specs"
74
+
75
+ python3 - "$JSON_MODE" "$REPO_ROOT" "$SPECS_DIR" << 'PY'
76
+ from __future__ import annotations
77
+
78
+ import json
79
+ import re
80
+ import sys
81
+ from pathlib import Path
82
+ from typing import Any
83
+
84
+
85
+ JSON_MODE = sys.argv[1].lower() == "true"
86
+ REPO_ROOT = Path(sys.argv[2])
87
+ SPECS_DIR = Path(sys.argv[3])
88
+
89
+ TASK_PATTERN = re.compile(r"^\s*-\s*\[(?P<state>[ xX])\]\s+(?P<id>[A-Z]\d{3,})\b")
90
+ FEATURE_PATTERN = re.compile(r"^(?P<num>\d{3})-")
91
+
92
+
93
+ def feature_sort_key(name: str) -> tuple[int, str]:
94
+ m = FEATURE_PATTERN.match(name)
95
+ if not m:
96
+ return (-1, name)
97
+ return (int(m.group("num")), name)
98
+
99
+
100
+ def count_todo(tasks_path: Path) -> dict[str, int]:
101
+ total = 0
102
+ done = 0
103
+ todo = 0
104
+
105
+ for line in tasks_path.read_text(encoding="utf-8", errors="replace").splitlines():
106
+ m = TASK_PATTERN.match(line)
107
+ if not m:
108
+ continue
109
+ total += 1
110
+ state = m.group("state").lower()
111
+ if state == "x":
112
+ done += 1
113
+ else:
114
+ todo += 1
115
+
116
+ return {"total": total, "done": done, "todo": todo}
117
+
118
+
119
+ if not SPECS_DIR.is_dir():
120
+ raise SystemExit(f"ERROR: specs dir not found: {SPECS_DIR}")
121
+
122
+ rows: list[dict[str, Any]] = []
123
+ total_todo = 0
124
+
125
+ for feature_dir in sorted((p for p in SPECS_DIR.iterdir() if p.is_dir()), key=lambda p: feature_sort_key(p.name)):
126
+ name = feature_dir.name
127
+ if not FEATURE_PATTERN.match(name):
128
+ continue
129
+
130
+ tasks_path = feature_dir / "tasks.md"
131
+ if not tasks_path.is_file():
132
+ continue
133
+
134
+ counts = count_todo(tasks_path)
135
+ if counts["todo"] <= 0:
136
+ continue
137
+
138
+ rows.append({"feature": name, "todo": counts["todo"]})
139
+ total_todo += counts["todo"]
140
+
141
+ if JSON_MODE:
142
+ print(json.dumps({"repoRoot": str(REPO_ROOT), "counts": {"specs": len(rows), "todo": total_todo}, "specs": rows}, ensure_ascii=False, indent=2))
143
+ raise SystemExit(0)
144
+
145
+ for r in rows:
146
+ print(f"{r['feature']} todo={r['todo']}")
147
+ print(f"TOTAL todo={total_todo} specs={len(rows)}")
148
+ PY
149
+
150
+ exit 0
151
+ fi
152
+
153
+ set +e
154
+ RAW_JSON="$("$EXTRACT" --json "${FORWARD_ARGS[@]}")"
155
+ STATUS=$?
156
+ set -e
157
+
158
+ TMP_JSON="$(mktemp -t speckit-todo-tasks.XXXXXX)"
159
+ trap 'rm -f "$TMP_JSON"' EXIT
160
+ printf '%s' "$RAW_JSON" > "$TMP_JSON"
161
+
162
+ python3 - "$JSON_MODE" "$TMP_JSON" << 'PY'
163
+ from __future__ import annotations
164
+
165
+ import json
166
+ import re
167
+ import sys
168
+ from pathlib import Path
169
+ from typing import Any
170
+
171
+
172
+ JSON_MODE = sys.argv[1].lower() == "true"
173
+ JSON_PATH = Path(sys.argv[2])
174
+
175
+ raw = JSON_PATH.read_text(encoding="utf-8", errors="replace")
176
+ try:
177
+ payload = json.loads(raw)
178
+ except json.JSONDecodeError:
179
+ sys.stdout.write(raw)
180
+ raise SystemExit(1)
181
+
182
+ targets: list[dict[str, Any]] = payload.get("targets") or []
183
+
184
+ TASK_DESC_PATTERN = re.compile(r"^\s*-\s*\[[ xX]\]\s+[A-Z]\d{3,}\b(?P<desc>.*)$")
185
+
186
+
187
+ def format_task(task: dict[str, Any]) -> str:
188
+ task_id = task.get("id") or "(unknown)"
189
+ line = task.get("line") or "?"
190
+ phase = task.get("phase")
191
+ section = task.get("section")
192
+ story = task.get("story")
193
+ refs = task.get("refs") or []
194
+ raw_line = (task.get("raw") or "").rstrip("\n")
195
+
196
+ m = TASK_DESC_PATTERN.match(raw_line)
197
+ desc = (m.group("desc").strip() if m else raw_line.strip()) or raw_line
198
+
199
+ ctx_parts = [p for p in [phase, section, story] if p]
200
+ ctx = " / ".join(ctx_parts)
201
+ ctx_part = f" [{ctx}]" if ctx else ""
202
+ refs_part = f" refs={','.join(refs)}" if refs else ""
203
+
204
+ return f" - {task_id}:{line}{ctx_part}{refs_part} {desc}"
205
+
206
+
207
+ filtered_targets: list[dict[str, Any]] = []
208
+ for t in targets:
209
+ if "error" in t:
210
+ filtered_targets.append(t)
211
+ continue
212
+
213
+ tasks = t.get("tasks") or []
214
+ todo_tasks = [task for task in tasks if not task.get("done")]
215
+
216
+ t2 = dict(t)
217
+ t2["tasks"] = todo_tasks
218
+ filtered_targets.append(t2)
219
+
220
+ if JSON_MODE:
221
+ print(json.dumps({"targets": filtered_targets}, ensure_ascii=False, indent=2))
222
+ raise SystemExit(0)
223
+
224
+ for t in filtered_targets:
225
+ if "error" in t:
226
+ err = t.get("error") or {}
227
+ print(f"[ERROR] {t.get('input')}: {err.get('type')}: {err.get('message')}")
228
+ continue
229
+
230
+ counts = t.get("counts") or {}
231
+ feature = t.get("feature") or "(unknown)"
232
+ tasks_file = t.get("tasksFile") or ""
233
+ print(f"{feature} ({tasks_file})")
234
+ print(
235
+ " "
236
+ + " ".join(
237
+ [
238
+ f"todo={counts.get('todo')}",
239
+ f"total={counts.get('total')}",
240
+ f"done={counts.get('done')}",
241
+ f"dup={counts.get('duplicates')}",
242
+ ]
243
+ )
244
+ )
245
+
246
+ todo_tasks = t.get("tasks") or []
247
+ if not todo_tasks:
248
+ print(" (no pending tasks)")
249
+ print()
250
+ continue
251
+
252
+ for task in todo_tasks:
253
+ print(format_task(task))
254
+ print()
255
+ PY
256
+
257
+ exit $STATUS