@mindfoldhq/trellis 0.1.9 → 0.2.1
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.
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +12 -6
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +669 -38
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/opencode.js +1 -1
- package/dist/configurators/opencode.js.map +1 -1
- package/dist/configurators/workflow.d.ts +4 -3
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +23 -20
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/constants/paths.d.ts +29 -30
- package/dist/constants/paths.d.ts.map +1 -1
- package/dist/constants/paths.js +32 -35
- package/dist/constants/paths.js.map +1 -1
- package/dist/migrations/index.d.ts +35 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +124 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/manifests/0.1.9.json +30 -0
- package/dist/migrations/manifests/0.2.0.json +43 -0
- package/dist/templates/claude/agents/check.md +3 -3
- package/dist/templates/claude/agents/debug.md +1 -1
- package/dist/templates/claude/agents/dispatch.md +12 -12
- package/dist/templates/claude/agents/implement.md +6 -6
- package/dist/templates/claude/agents/plan.md +37 -37
- package/dist/templates/claude/agents/research.md +1 -1
- package/dist/templates/claude/commands/before-backend-dev.md +5 -5
- package/dist/templates/claude/commands/before-frontend-dev.md +5 -5
- package/dist/templates/claude/commands/break-loop.md +2 -2
- package/dist/templates/claude/commands/check-backend.md +6 -6
- package/dist/templates/claude/commands/check-cross-layer.md +5 -5
- package/dist/templates/claude/commands/check-frontend.md +6 -6
- package/dist/templates/claude/commands/create-command.md +3 -3
- package/dist/templates/claude/commands/finish-work.md +6 -6
- package/dist/templates/claude/commands/integrate-skill.md +11 -11
- package/dist/templates/claude/commands/{onboard-developer.md → onboard.md} +31 -28
- package/dist/templates/claude/commands/parallel.md +17 -17
- package/dist/templates/claude/commands/{record-agent-flow.md → record-session.md} +7 -7
- package/dist/templates/claude/commands/start.md +36 -36
- package/dist/templates/claude/hooks/inject-subagent-context.py +77 -76
- package/dist/templates/claude/hooks/ralph-loop.py +18 -18
- package/dist/templates/claude/hooks/session-start.py +4 -4
- package/dist/templates/cursor/commands/before-backend-dev.md +5 -5
- package/dist/templates/cursor/commands/before-frontend-dev.md +5 -5
- package/dist/templates/cursor/commands/break-loop.md +2 -2
- package/dist/templates/cursor/commands/check-backend.md +6 -6
- package/dist/templates/cursor/commands/check-cross-layer.md +5 -5
- package/dist/templates/cursor/commands/check-frontend.md +6 -6
- package/dist/templates/cursor/commands/create-command.md +3 -3
- package/dist/templates/cursor/commands/finish-work.md +6 -6
- package/dist/templates/cursor/commands/integrate-skill.md +11 -11
- package/dist/templates/cursor/commands/{onboard-developer.md → onboard.md} +31 -28
- package/dist/templates/cursor/commands/{record-agent-flow.md → record-session.md} +7 -7
- package/dist/templates/cursor/commands/start.md +25 -25
- package/dist/templates/extract.d.ts +2 -2
- package/dist/templates/extract.js +2 -2
- package/dist/templates/markdown/agents.md +2 -2
- package/dist/templates/markdown/gitignore.txt +2 -2
- package/dist/templates/markdown/index.d.ts +1 -0
- package/dist/templates/markdown/index.d.ts.map +1 -1
- package/dist/templates/markdown/index.js +4 -2
- package/dist/templates/markdown/index.js.map +1 -1
- package/dist/templates/markdown/{agent-traces-index.md → workspace-index.md} +14 -14
- package/dist/templates/trellis/index.d.ts +7 -1
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +14 -2
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/add-session.sh +26 -26
- package/dist/templates/trellis/scripts/common/developer.sh +20 -21
- package/dist/templates/trellis/scripts/common/git-context.sh +90 -115
- package/dist/templates/trellis/scripts/common/paths.sh +53 -63
- package/dist/templates/trellis/scripts/common/phase.sh +40 -40
- package/dist/templates/trellis/scripts/common/registry.sh +13 -13
- package/dist/templates/trellis/scripts/common/task-queue.sh +142 -0
- package/dist/templates/trellis/scripts/common/task-utils.sh +151 -0
- package/dist/templates/trellis/scripts/common/worktree.sh +3 -3
- package/dist/templates/trellis/scripts/create-bootstrap.sh +43 -42
- package/dist/templates/trellis/scripts/init-developer.sh +1 -1
- package/dist/templates/trellis/scripts/multi-agent/cleanup.sh +33 -33
- package/dist/templates/trellis/scripts/multi-agent/create-pr.sh +30 -30
- package/dist/templates/trellis/scripts/multi-agent/plan.sh +28 -28
- package/dist/templates/trellis/scripts/multi-agent/start.sh +56 -56
- package/dist/templates/trellis/scripts/multi-agent/status.sh +59 -59
- package/dist/templates/trellis/scripts/{feature.sh → task.sh} +235 -185
- package/dist/templates/trellis/workflow.md +71 -74
- package/dist/types/migration.d.ts +74 -0
- package/dist/types/migration.d.ts.map +1 -0
- package/dist/types/migration.js +8 -0
- package/dist/types/migration.js.map +1 -0
- package/dist/utils/template-hash.d.ts +78 -0
- package/dist/utils/template-hash.d.ts.map +1 -0
- package/dist/utils/template-hash.js +234 -0
- package/dist/utils/template-hash.js.map +1 -0
- package/package.json +1 -1
- package/dist/templates/trellis/scripts/common/backlog.sh +0 -220
- package/dist/templates/trellis/scripts/common/feature-utils.sh +0 -194
- /package/dist/templates/trellis/{backlog → tasks}/.gitkeep +0 -0
|
@@ -7,57 +7,57 @@
|
|
|
7
7
|
# Usage:
|
|
8
8
|
# source common/phase.sh
|
|
9
9
|
#
|
|
10
|
-
# get_current_phase "$
|
|
11
|
-
# get_total_phases "$
|
|
12
|
-
# get_phase_action "$
|
|
13
|
-
# get_phase_info "$
|
|
14
|
-
# set_phase "$
|
|
15
|
-
# advance_phase "$
|
|
16
|
-
# get_phase_for_action "$
|
|
10
|
+
# get_current_phase "$task_json" # Returns current phase number
|
|
11
|
+
# get_total_phases "$task_json" # Returns total phase count
|
|
12
|
+
# get_phase_action "$task_json" "$phase" # Returns action name for phase
|
|
13
|
+
# get_phase_info "$task_json" # Returns "N/M (action)" format
|
|
14
|
+
# set_phase "$task_json" "$phase" # Sets current_phase
|
|
15
|
+
# advance_phase "$task_json" # Advances to next phase
|
|
16
|
+
# get_phase_for_action "$task_json" "$action" # Returns phase number for action
|
|
17
17
|
# =============================================================================
|
|
18
18
|
|
|
19
19
|
# Get current phase number
|
|
20
20
|
get_current_phase() {
|
|
21
|
-
local
|
|
22
|
-
if [ ! -f "$
|
|
21
|
+
local task_json="$1"
|
|
22
|
+
if [ ! -f "$task_json" ]; then
|
|
23
23
|
echo "0"
|
|
24
24
|
return
|
|
25
25
|
fi
|
|
26
|
-
jq -r '.current_phase // 0' "$
|
|
26
|
+
jq -r '.current_phase // 0' "$task_json"
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
# Get total number of phases
|
|
30
30
|
get_total_phases() {
|
|
31
|
-
local
|
|
32
|
-
if [ ! -f "$
|
|
31
|
+
local task_json="$1"
|
|
32
|
+
if [ ! -f "$task_json" ]; then
|
|
33
33
|
echo "0"
|
|
34
34
|
return
|
|
35
35
|
fi
|
|
36
|
-
jq -r '.next_action | length // 0' "$
|
|
36
|
+
jq -r '.next_action | length // 0' "$task_json"
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
# Get action name for a specific phase
|
|
40
40
|
get_phase_action() {
|
|
41
|
-
local
|
|
41
|
+
local task_json="$1"
|
|
42
42
|
local phase="$2"
|
|
43
|
-
if [ ! -f "$
|
|
43
|
+
if [ ! -f "$task_json" ]; then
|
|
44
44
|
echo "unknown"
|
|
45
45
|
return
|
|
46
46
|
fi
|
|
47
|
-
jq -r --argjson phase "$phase" '.next_action[] | select(.phase == $phase) | .action // "unknown"' "$
|
|
47
|
+
jq -r --argjson phase "$phase" '.next_action[] | select(.phase == $phase) | .action // "unknown"' "$task_json"
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
# Get formatted phase info: "N/M (action)"
|
|
51
51
|
get_phase_info() {
|
|
52
|
-
local
|
|
53
|
-
if [ ! -f "$
|
|
52
|
+
local task_json="$1"
|
|
53
|
+
if [ ! -f "$task_json" ]; then
|
|
54
54
|
echo "N/A"
|
|
55
55
|
return
|
|
56
56
|
fi
|
|
57
57
|
|
|
58
|
-
local current_phase=$(get_current_phase "$
|
|
59
|
-
local total_phases=$(get_total_phases "$
|
|
60
|
-
local action_name=$(get_phase_action "$
|
|
58
|
+
local current_phase=$(get_current_phase "$task_json")
|
|
59
|
+
local total_phases=$(get_total_phases "$task_json")
|
|
60
|
+
local action_name=$(get_phase_action "$task_json" "$current_phase")
|
|
61
61
|
|
|
62
62
|
if [ "$current_phase" = "0" ] || [ "$current_phase" = "null" ]; then
|
|
63
63
|
echo "0/${total_phases} (pending)"
|
|
@@ -68,29 +68,29 @@ get_phase_info() {
|
|
|
68
68
|
|
|
69
69
|
# Set current phase to a specific value
|
|
70
70
|
set_phase() {
|
|
71
|
-
local
|
|
71
|
+
local task_json="$1"
|
|
72
72
|
local phase="$2"
|
|
73
73
|
|
|
74
|
-
if [ ! -f "$
|
|
75
|
-
echo "Error:
|
|
74
|
+
if [ ! -f "$task_json" ]; then
|
|
75
|
+
echo "Error: task.json not found: $task_json" >&2
|
|
76
76
|
return 1
|
|
77
77
|
fi
|
|
78
78
|
|
|
79
|
-
jq --argjson phase "$phase" '.current_phase = $phase' "$
|
|
80
|
-
mv "${
|
|
79
|
+
jq --argjson phase "$phase" '.current_phase = $phase' "$task_json" > "${task_json}.tmp"
|
|
80
|
+
mv "${task_json}.tmp" "$task_json"
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
# Advance to next phase
|
|
84
84
|
advance_phase() {
|
|
85
|
-
local
|
|
85
|
+
local task_json="$1"
|
|
86
86
|
|
|
87
|
-
if [ ! -f "$
|
|
88
|
-
echo "Error:
|
|
87
|
+
if [ ! -f "$task_json" ]; then
|
|
88
|
+
echo "Error: task.json not found: $task_json" >&2
|
|
89
89
|
return 1
|
|
90
90
|
fi
|
|
91
91
|
|
|
92
|
-
local current=$(get_current_phase "$
|
|
93
|
-
local total=$(get_total_phases "$
|
|
92
|
+
local current=$(get_current_phase "$task_json")
|
|
93
|
+
local total=$(get_total_phases "$task_json")
|
|
94
94
|
local next=$((current + 1))
|
|
95
95
|
|
|
96
96
|
if [ "$next" -gt "$total" ]; then
|
|
@@ -98,20 +98,20 @@ advance_phase() {
|
|
|
98
98
|
return 0
|
|
99
99
|
fi
|
|
100
100
|
|
|
101
|
-
set_phase "$
|
|
101
|
+
set_phase "$task_json" "$next"
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
# Get phase number for a specific action name
|
|
105
105
|
get_phase_for_action() {
|
|
106
|
-
local
|
|
106
|
+
local task_json="$1"
|
|
107
107
|
local action="$2"
|
|
108
108
|
|
|
109
|
-
if [ ! -f "$
|
|
109
|
+
if [ ! -f "$task_json" ]; then
|
|
110
110
|
echo "0"
|
|
111
111
|
return
|
|
112
112
|
fi
|
|
113
113
|
|
|
114
|
-
jq -r --arg action "$action" '.next_action[] | select(.action == $action) | .phase // 0' "$
|
|
114
|
+
jq -r --arg action "$action" '.next_action[] | select(.action == $action) | .phase // 0' "$task_json"
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
# Map subagent type to action name
|
|
@@ -131,20 +131,20 @@ map_subagent_to_action() {
|
|
|
131
131
|
|
|
132
132
|
# Check if a phase is completed (current_phase > phase)
|
|
133
133
|
is_phase_completed() {
|
|
134
|
-
local
|
|
134
|
+
local task_json="$1"
|
|
135
135
|
local phase="$2"
|
|
136
136
|
|
|
137
|
-
local current=$(get_current_phase "$
|
|
137
|
+
local current=$(get_current_phase "$task_json")
|
|
138
138
|
[ "$current" -gt "$phase" ]
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
# Check if we're at a specific action
|
|
142
142
|
is_current_action() {
|
|
143
|
-
local
|
|
143
|
+
local task_json="$1"
|
|
144
144
|
local action="$2"
|
|
145
145
|
|
|
146
|
-
local current=$(get_current_phase "$
|
|
147
|
-
local action_phase=$(get_phase_for_action "$
|
|
146
|
+
local current=$(get_current_phase "$task_json")
|
|
147
|
+
local action_phase=$(get_phase_for_action "$task_json" "$action")
|
|
148
148
|
|
|
149
149
|
[ "$current" = "$action_phase" ]
|
|
150
150
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
# registry_get_file - Get registry file path
|
|
9
9
|
# registry_get_agent_by_id - Find agent by ID
|
|
10
10
|
# registry_get_agent_by_worktree - Find agent by worktree path
|
|
11
|
-
#
|
|
11
|
+
# registry_get_task_dir - Get feature dir for a worktree
|
|
12
12
|
# registry_remove_by_id - Remove agent by ID
|
|
13
13
|
# registry_remove_by_worktree - Remove agent by worktree path
|
|
14
14
|
# registry_add_agent - Add agent to registry
|
|
@@ -99,7 +99,7 @@ registry_get_agent_by_worktree() {
|
|
|
99
99
|
return 1
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
# Search agent by ID or
|
|
102
|
+
# Search agent by ID or task_dir containing search term
|
|
103
103
|
# Args: search_term, [repo_root]
|
|
104
104
|
# Returns: first matching agent JSON object (compact), or empty if not found
|
|
105
105
|
registry_search_agent() {
|
|
@@ -112,7 +112,7 @@ registry_search_agent() {
|
|
|
112
112
|
fi
|
|
113
113
|
|
|
114
114
|
local agent=$(jq -c --arg search "$search" \
|
|
115
|
-
'[.agents[] | select(.id == $search or (.
|
|
115
|
+
'[.agents[] | select(.id == $search or (.task_dir | contains($search)))] | first' \
|
|
116
116
|
"$registry_file" 2>/dev/null)
|
|
117
117
|
|
|
118
118
|
if [[ -n "$agent" ]] && [[ "$agent" != "null" ]]; then
|
|
@@ -125,8 +125,8 @@ registry_search_agent() {
|
|
|
125
125
|
|
|
126
126
|
# Get feature directory for a worktree
|
|
127
127
|
# Args: worktree_path, [repo_root]
|
|
128
|
-
# Returns:
|
|
129
|
-
|
|
128
|
+
# Returns: task_dir value, or empty if not found
|
|
129
|
+
registry_get_task_dir() {
|
|
130
130
|
local worktree_path="$1"
|
|
131
131
|
local repo_root="${2:-$(get_repo_root)}"
|
|
132
132
|
local registry_file=$(registry_get_file "$repo_root")
|
|
@@ -135,12 +135,12 @@ registry_get_feature_dir() {
|
|
|
135
135
|
return 1
|
|
136
136
|
fi
|
|
137
137
|
|
|
138
|
-
local
|
|
139
|
-
'.agents[] | select(.worktree_path == $path) | .
|
|
138
|
+
local task_dir=$(jq -r --arg path "$worktree_path" \
|
|
139
|
+
'.agents[] | select(.worktree_path == $path) | .task_dir' \
|
|
140
140
|
"$registry_file" 2>/dev/null)
|
|
141
141
|
|
|
142
|
-
if [[ -n "$
|
|
143
|
-
echo "$
|
|
142
|
+
if [[ -n "$task_dir" ]] && [[ "$task_dir" != "null" ]]; then
|
|
143
|
+
echo "$task_dir"
|
|
144
144
|
return 0
|
|
145
145
|
fi
|
|
146
146
|
|
|
@@ -192,13 +192,13 @@ registry_remove_by_worktree() {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
# Add agent to registry (replaces if same ID exists)
|
|
195
|
-
# Args: agent_id, worktree_path, pid,
|
|
195
|
+
# Args: agent_id, worktree_path, pid, task_dir, [repo_root]
|
|
196
196
|
# Returns: 0 on success
|
|
197
197
|
registry_add_agent() {
|
|
198
198
|
local agent_id="$1"
|
|
199
199
|
local worktree_path="$2"
|
|
200
200
|
local pid="$3"
|
|
201
|
-
local
|
|
201
|
+
local task_dir="$4"
|
|
202
202
|
local repo_root="${5:-$(get_repo_root)}"
|
|
203
203
|
|
|
204
204
|
_ensure_registry "$repo_root"
|
|
@@ -217,13 +217,13 @@ registry_add_agent() {
|
|
|
217
217
|
--arg worktree "$worktree_path" \
|
|
218
218
|
--arg pid "$pid" \
|
|
219
219
|
--arg started_at "$started_at" \
|
|
220
|
-
--arg
|
|
220
|
+
--arg task_dir "$task_dir" \
|
|
221
221
|
'{
|
|
222
222
|
id: $id,
|
|
223
223
|
worktree_path: $worktree,
|
|
224
224
|
pid: ($pid | tonumber),
|
|
225
225
|
started_at: $started_at,
|
|
226
|
-
|
|
226
|
+
task_dir: $task_dir
|
|
227
227
|
}')
|
|
228
228
|
|
|
229
229
|
# Add to registry
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Task queue utility functions
|
|
3
|
+
#
|
|
4
|
+
# Usage: source this file in other scripts
|
|
5
|
+
# source "$(dirname "$0")/common/task-queue.sh"
|
|
6
|
+
#
|
|
7
|
+
# Provides:
|
|
8
|
+
# list_pending_tasks - List tasks with pending status
|
|
9
|
+
# get_task_stats - Get P0/P1/P2/P3 counts
|
|
10
|
+
|
|
11
|
+
# Ensure paths.sh is loaded
|
|
12
|
+
if ! type get_repo_root &>/dev/null; then
|
|
13
|
+
echo "Error: paths.sh must be sourced before task-queue.sh" >&2
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# =============================================================================
|
|
18
|
+
# Public Functions
|
|
19
|
+
# =============================================================================
|
|
20
|
+
|
|
21
|
+
# List tasks by status
|
|
22
|
+
# Args: [filter_status]
|
|
23
|
+
# Output: formatted list to stdout
|
|
24
|
+
list_tasks_by_status() {
|
|
25
|
+
local filter_status="${1:-}"
|
|
26
|
+
local repo_root="${2:-$(get_repo_root)}"
|
|
27
|
+
|
|
28
|
+
local tasks_dir=$(get_tasks_dir "$repo_root")
|
|
29
|
+
|
|
30
|
+
if [[ ! -d "$tasks_dir" ]]; then
|
|
31
|
+
return 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
for d in "$tasks_dir"/*/; do
|
|
35
|
+
if [[ -d "$d" ]] && [[ "$(basename "$d")" != "archive" ]]; then
|
|
36
|
+
local task_json="$d/$FILE_TASK_JSON"
|
|
37
|
+
if [[ -f "$task_json" ]]; then
|
|
38
|
+
local id=$(jq -r '.id' "$task_json")
|
|
39
|
+
local title=$(jq -r '.title // .name' "$task_json")
|
|
40
|
+
local priority=$(jq -r '.priority // "P2"' "$task_json")
|
|
41
|
+
local status=$(jq -r '.status // "planning"' "$task_json")
|
|
42
|
+
local assignee=$(jq -r '.assignee // "-"' "$task_json")
|
|
43
|
+
|
|
44
|
+
# Apply filter
|
|
45
|
+
if [[ -n "$filter_status" ]] && [[ "$status" != "$filter_status" ]]; then
|
|
46
|
+
continue
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
echo "$priority|$id|$title|$status|$assignee"
|
|
50
|
+
fi
|
|
51
|
+
fi
|
|
52
|
+
done
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# List pending tasks
|
|
56
|
+
list_pending_tasks() {
|
|
57
|
+
list_tasks_by_status "planning" "$@"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# List tasks assigned to a specific developer
|
|
61
|
+
# Args: developer_name, [filter_status], [repo_root]
|
|
62
|
+
# Output: formatted list to stdout
|
|
63
|
+
list_tasks_by_assignee() {
|
|
64
|
+
local assignee="$1"
|
|
65
|
+
local filter_status="${2:-}"
|
|
66
|
+
local repo_root="${3:-$(get_repo_root)}"
|
|
67
|
+
|
|
68
|
+
local tasks_dir=$(get_tasks_dir "$repo_root")
|
|
69
|
+
|
|
70
|
+
if [[ ! -d "$tasks_dir" ]]; then
|
|
71
|
+
return 0
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
for d in "$tasks_dir"/*/; do
|
|
75
|
+
if [[ -d "$d" ]] && [[ "$(basename "$d")" != "archive" ]]; then
|
|
76
|
+
local task_json="$d/$FILE_TASK_JSON"
|
|
77
|
+
if [[ -f "$task_json" ]]; then
|
|
78
|
+
local id=$(jq -r '.id' "$task_json")
|
|
79
|
+
local title=$(jq -r '.title // .name' "$task_json")
|
|
80
|
+
local priority=$(jq -r '.priority // "P2"' "$task_json")
|
|
81
|
+
local status=$(jq -r '.status // "planning"' "$task_json")
|
|
82
|
+
local task_assignee=$(jq -r '.assignee // "-"' "$task_json")
|
|
83
|
+
|
|
84
|
+
# Apply assignee filter
|
|
85
|
+
if [[ "$task_assignee" != "$assignee" ]]; then
|
|
86
|
+
continue
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# Apply status filter
|
|
90
|
+
if [[ -n "$filter_status" ]] && [[ "$status" != "$filter_status" ]]; then
|
|
91
|
+
continue
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
echo "$priority|$id|$title|$status|$task_assignee"
|
|
95
|
+
fi
|
|
96
|
+
fi
|
|
97
|
+
done
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# List my tasks (current developer)
|
|
101
|
+
# Args: [filter_status], [repo_root]
|
|
102
|
+
list_my_tasks() {
|
|
103
|
+
local filter_status="${1:-}"
|
|
104
|
+
local repo_root="${2:-$(get_repo_root)}"
|
|
105
|
+
local developer=$(get_developer "$repo_root")
|
|
106
|
+
|
|
107
|
+
if [[ -z "$developer" ]]; then
|
|
108
|
+
echo "Error: Developer not set" >&2
|
|
109
|
+
return 1
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
list_tasks_by_assignee "$developer" "$filter_status" "$repo_root"
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# Get task statistics
|
|
116
|
+
# Output: "P0:N P1:N P2:N P3:N Total:N"
|
|
117
|
+
get_task_stats() {
|
|
118
|
+
local repo_root="${1:-$(get_repo_root)}"
|
|
119
|
+
local tasks_dir=$(get_tasks_dir "$repo_root")
|
|
120
|
+
|
|
121
|
+
local p0=0 p1=0 p2=0 p3=0 total=0
|
|
122
|
+
|
|
123
|
+
if [[ -d "$tasks_dir" ]]; then
|
|
124
|
+
for d in "$tasks_dir"/*/; do
|
|
125
|
+
if [[ -d "$d" ]] && [[ "$(basename "$d")" != "archive" ]]; then
|
|
126
|
+
local task_json="$d/$FILE_TASK_JSON"
|
|
127
|
+
if [[ -f "$task_json" ]]; then
|
|
128
|
+
local priority=$(jq -r '.priority // "P2"' "$task_json" 2>/dev/null)
|
|
129
|
+
case "$priority" in
|
|
130
|
+
P0) ((p0++)) ;;
|
|
131
|
+
P1) ((p1++)) ;;
|
|
132
|
+
P2) ((p2++)) ;;
|
|
133
|
+
P3) ((p3++)) ;;
|
|
134
|
+
esac
|
|
135
|
+
((total++))
|
|
136
|
+
fi
|
|
137
|
+
fi
|
|
138
|
+
done
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
echo "P0:$p0 P1:$p1 P2:$p2 P3:$p3 Total:$total"
|
|
142
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Task utility functions
|
|
3
|
+
#
|
|
4
|
+
# Usage: source this file in other scripts
|
|
5
|
+
# source "$(dirname "$0")/common/task-utils.sh"
|
|
6
|
+
#
|
|
7
|
+
# Provides:
|
|
8
|
+
# is_safe_task_path - Validate task path is safe to operate on
|
|
9
|
+
# find_task_by_name - Find task directory by name
|
|
10
|
+
# archive_task_dir - Archive task to monthly directory
|
|
11
|
+
|
|
12
|
+
# Ensure dependencies are loaded
|
|
13
|
+
if ! type get_repo_root &>/dev/null; then
|
|
14
|
+
echo "Error: paths.sh must be sourced before task-utils.sh" >&2
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# =============================================================================
|
|
19
|
+
# Path Safety
|
|
20
|
+
# =============================================================================
|
|
21
|
+
|
|
22
|
+
# Check if a relative task path is safe to operate on
|
|
23
|
+
# Args: task_path (relative), repo_root
|
|
24
|
+
# Returns: 0 if safe, 1 if dangerous
|
|
25
|
+
# Outputs: error message to stderr if unsafe
|
|
26
|
+
is_safe_task_path() {
|
|
27
|
+
local task_path="$1"
|
|
28
|
+
local repo_root="${2:-$(get_repo_root)}"
|
|
29
|
+
|
|
30
|
+
# Check empty or null
|
|
31
|
+
if [[ -z "$task_path" ]] || [[ "$task_path" = "null" ]]; then
|
|
32
|
+
echo "Error: empty or null task path" >&2
|
|
33
|
+
return 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Reject absolute paths
|
|
37
|
+
if [[ "$task_path" = /* ]]; then
|
|
38
|
+
echo "Error: absolute path not allowed: $task_path" >&2
|
|
39
|
+
return 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# Reject ".", "..", paths starting with "./" or "../", or containing ".."
|
|
43
|
+
if [[ "$task_path" = "." ]] || [[ "$task_path" = ".." ]] || \
|
|
44
|
+
[[ "$task_path" = "./" ]] || [[ "$task_path" == ./* ]] || \
|
|
45
|
+
[[ "$task_path" == *".."* ]]; then
|
|
46
|
+
echo "Error: path traversal not allowed: $task_path" >&2
|
|
47
|
+
return 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Final check: ensure resolved path is not the repo root
|
|
51
|
+
local abs_path="${repo_root}/${task_path}"
|
|
52
|
+
if [[ -e "$abs_path" ]]; then
|
|
53
|
+
local resolved=$(realpath "$abs_path" 2>/dev/null)
|
|
54
|
+
local root_resolved=$(realpath "$repo_root" 2>/dev/null)
|
|
55
|
+
if [[ "$resolved" = "$root_resolved" ]]; then
|
|
56
|
+
echo "Error: path resolves to repo root: $task_path" >&2
|
|
57
|
+
return 1
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
return 0
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# =============================================================================
|
|
65
|
+
# Task Lookup
|
|
66
|
+
# =============================================================================
|
|
67
|
+
|
|
68
|
+
# Find task directory by name (exact or suffix match)
|
|
69
|
+
# Args: task_name, tasks_dir
|
|
70
|
+
# Returns: absolute path to task directory, or empty if not found
|
|
71
|
+
find_task_by_name() {
|
|
72
|
+
local task_name="$1"
|
|
73
|
+
local tasks_dir="$2"
|
|
74
|
+
|
|
75
|
+
if [[ -z "$task_name" ]] || [[ -z "$tasks_dir" ]]; then
|
|
76
|
+
return 1
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# Try exact match first
|
|
80
|
+
local task_dir=$(find "$tasks_dir" -maxdepth 1 -type d -name "${task_name}" 2>/dev/null | head -1)
|
|
81
|
+
|
|
82
|
+
# Try suffix match (e.g., "my-task" matches "01-21-my-task")
|
|
83
|
+
if [[ -z "$task_dir" ]]; then
|
|
84
|
+
task_dir=$(find "$tasks_dir" -maxdepth 1 -type d -name "*-${task_name}" 2>/dev/null | head -1)
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
if [[ -n "$task_dir" ]] && [[ -d "$task_dir" ]]; then
|
|
88
|
+
echo "$task_dir"
|
|
89
|
+
return 0
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
return 1
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# =============================================================================
|
|
96
|
+
# Archive Operations
|
|
97
|
+
# =============================================================================
|
|
98
|
+
|
|
99
|
+
# Archive a task directory to archive/{YYYY-MM}/
|
|
100
|
+
# Args: task_dir_abs, [repo_root]
|
|
101
|
+
# Returns: 0 on success, 1 on error
|
|
102
|
+
# Outputs: archive destination path
|
|
103
|
+
archive_task_dir() {
|
|
104
|
+
local task_dir_abs="$1"
|
|
105
|
+
local repo_root="${2:-$(get_repo_root)}"
|
|
106
|
+
|
|
107
|
+
if [[ ! -d "$task_dir_abs" ]]; then
|
|
108
|
+
echo "Error: task directory not found: $task_dir_abs" >&2
|
|
109
|
+
return 1
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# Get tasks directory (parent of the task)
|
|
113
|
+
local tasks_dir=$(dirname "$task_dir_abs")
|
|
114
|
+
local archive_dir="$tasks_dir/archive"
|
|
115
|
+
local year_month=$(date +%Y-%m)
|
|
116
|
+
local month_dir="$archive_dir/$year_month"
|
|
117
|
+
|
|
118
|
+
# Create archive directory
|
|
119
|
+
mkdir -p "$month_dir"
|
|
120
|
+
|
|
121
|
+
# Move task to archive
|
|
122
|
+
local task_name=$(basename "$task_dir_abs")
|
|
123
|
+
mv "$task_dir_abs" "$month_dir/"
|
|
124
|
+
|
|
125
|
+
# Output the destination
|
|
126
|
+
echo "$month_dir/$task_name"
|
|
127
|
+
return 0
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# Complete archive workflow: archive directory
|
|
131
|
+
# Args: task_dir_abs, [repo_root]
|
|
132
|
+
# Returns: 0 on success
|
|
133
|
+
# Outputs: lines with status info
|
|
134
|
+
archive_task_complete() {
|
|
135
|
+
local task_dir_abs="$1"
|
|
136
|
+
local repo_root="${2:-$(get_repo_root)}"
|
|
137
|
+
|
|
138
|
+
if [[ ! -d "$task_dir_abs" ]]; then
|
|
139
|
+
echo "Error: task directory not found: $task_dir_abs" >&2
|
|
140
|
+
return 1
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Archive the directory
|
|
144
|
+
local archive_dest
|
|
145
|
+
if archive_dest=$(archive_task_dir "$task_dir_abs" "$repo_root"); then
|
|
146
|
+
echo "archived_to:$archive_dest"
|
|
147
|
+
return 0
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
return 1
|
|
151
|
+
}
|
|
@@ -120,9 +120,9 @@ get_worktree_post_create_hooks() {
|
|
|
120
120
|
# Returns: absolute path to agents directory
|
|
121
121
|
get_agents_dir() {
|
|
122
122
|
local repo_root="${1:-$(get_repo_root)}"
|
|
123
|
-
local
|
|
123
|
+
local workspace_dir=$(get_workspace_dir "$repo_root")
|
|
124
124
|
|
|
125
|
-
if [[ -n "$
|
|
126
|
-
echo "$
|
|
125
|
+
if [[ -n "$workspace_dir" ]]; then
|
|
126
|
+
echo "$workspace_dir/.agents"
|
|
127
127
|
fi
|
|
128
128
|
}
|