sdd-agent-pack 1.3.4 → 1.3.6
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.
|
@@ -20,8 +20,13 @@ LLM Configuration:
|
|
|
20
20
|
LLM_API_KEY — API key from your LLM provider
|
|
21
21
|
|
|
22
22
|
Optional:
|
|
23
|
-
LLM_MODEL
|
|
24
|
-
LLM_BASE_URL
|
|
23
|
+
LLM_MODEL — Model name (default: openrouter/anthropic/claude-sonnet-4-5-20250929)
|
|
24
|
+
LLM_BASE_URL — Provider base URL
|
|
25
|
+
SANDBOX_VOLUMES — Mount host dirs into Docker sandbox (format: host:container[:mode])
|
|
26
|
+
Required when using --cloud (agent-server/sandbox mode) so
|
|
27
|
+
file changes persist on the host filesystem.
|
|
28
|
+
Example: SANDBOX_VOLUMES=$PWD:/workspace:rw
|
|
29
|
+
SANDBOX_USER_ID — Host user ID for sandbox file ownership (default: $(id -u))
|
|
25
30
|
|
|
26
31
|
Examples:
|
|
27
32
|
OpenRouter:
|
|
@@ -61,7 +66,16 @@ TASKS_FILE = "specs/tasks.md"
|
|
|
61
66
|
|
|
62
67
|
|
|
63
68
|
def parse_epics(file_path: str) -> list[dict]:
|
|
64
|
-
"""Parse incomplete epics from tasks.md
|
|
69
|
+
"""Parse incomplete epics from tasks.md.
|
|
70
|
+
|
|
71
|
+
Detects epic headings like:
|
|
72
|
+
## Epic EPIC-001 — Title
|
|
73
|
+
## Epic TDS.1: Title
|
|
74
|
+
### Epic Something
|
|
75
|
+
|
|
76
|
+
An epic is "incomplete" if any checkbox (- [ ]) is unchecked under it.
|
|
77
|
+
An epic is "complete" if all its checkboxes are checked or there are none.
|
|
78
|
+
"""
|
|
65
79
|
if not os.path.exists(file_path):
|
|
66
80
|
print(f"✗ tasks.md not found at {file_path}")
|
|
67
81
|
print(" Run this from your repository root.")
|
|
@@ -71,43 +85,35 @@ def parse_epics(file_path: str) -> list[dict]:
|
|
|
71
85
|
content = f.read()
|
|
72
86
|
|
|
73
87
|
epics = []
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
r"status:\s*(pending|in-progress)", line
|
|
104
|
-
):
|
|
105
|
-
epics.append(
|
|
106
|
-
{"id": current_epic, "title": current_epic, "status": "pending"}
|
|
107
|
-
)
|
|
108
|
-
current_epic = None
|
|
109
|
-
elif current_epic and re.search(r"status:\s*complete", line):
|
|
110
|
-
current_epic = None
|
|
88
|
+
lines = content.split("\n")
|
|
89
|
+
|
|
90
|
+
epic_heading_re = re.compile(r"^#{2,4}\s+Epic\s+(.+)$", re.IGNORECASE)
|
|
91
|
+
|
|
92
|
+
current_epic = None
|
|
93
|
+
has_unchecked = False
|
|
94
|
+
|
|
95
|
+
for line in lines:
|
|
96
|
+
heading_match = epic_heading_re.match(line)
|
|
97
|
+
if heading_match:
|
|
98
|
+
# Save previous epic if it has unchecked tasks
|
|
99
|
+
if current_epic and has_unchecked:
|
|
100
|
+
epics.append({
|
|
101
|
+
"id": current_epic,
|
|
102
|
+
"title": current_epic,
|
|
103
|
+
"status": "incomplete",
|
|
104
|
+
})
|
|
105
|
+
current_epic = heading_match.group(1).strip()
|
|
106
|
+
has_unchecked = False
|
|
107
|
+
elif current_epic and re.match(r"^\s*-\s+\[\s*\]", line):
|
|
108
|
+
has_unchecked = True
|
|
109
|
+
|
|
110
|
+
# Don't forget the last epic
|
|
111
|
+
if current_epic and has_unchecked:
|
|
112
|
+
epics.append({
|
|
113
|
+
"id": current_epic,
|
|
114
|
+
"title": current_epic,
|
|
115
|
+
"status": "incomplete",
|
|
116
|
+
})
|
|
111
117
|
|
|
112
118
|
return epics
|
|
113
119
|
|
|
@@ -149,6 +155,22 @@ def run_epic(epic: dict, use_cloud: bool) -> bool:
|
|
|
149
155
|
print(" See .sdd-agent-pack/templates/env.example for all options.")
|
|
150
156
|
return False
|
|
151
157
|
|
|
158
|
+
# Ensure the sandbox can access the local project directory.
|
|
159
|
+
# In local (SDK) mode the Conversation(workspace=cwd) creates a
|
|
160
|
+
# LocalWorkspace that runs subprocesses directly on the host, so
|
|
161
|
+
# file changes persist automatically.
|
|
162
|
+
#
|
|
163
|
+
# When --cloud is used the agent runs in a remote Docker sandbox.
|
|
164
|
+
# In that case SANDBOX_VOLUMES mounts the host project into the
|
|
165
|
+
# container's /workspace so agent file changes survive.
|
|
166
|
+
if use_cloud:
|
|
167
|
+
sandbox_volumes = os.getenv(
|
|
168
|
+
"SANDBOX_VOLUMES",
|
|
169
|
+
f"{os.getcwd()}:/workspace:rw",
|
|
170
|
+
)
|
|
171
|
+
os.environ.setdefault("SANDBOX_VOLUMES", sandbox_volumes)
|
|
172
|
+
os.environ.setdefault("SANDBOX_USER_ID", str(os.getuid()))
|
|
173
|
+
|
|
152
174
|
llm = LLM(
|
|
153
175
|
model=model,
|
|
154
176
|
api_key=api_key,
|
|
@@ -68,24 +68,36 @@ echo "━━━ SDD Agent Pack — Epic Orchestrator ━━━"
|
|
|
68
68
|
echo ""
|
|
69
69
|
|
|
70
70
|
# Parse incomplete epics from tasks.md
|
|
71
|
-
# Looks for
|
|
71
|
+
# Looks for epic headings (## Epic XXX — Title) and checks if any
|
|
72
|
+
# of their tasks or requirements have unchecked boxes (- [ ]).
|
|
73
|
+
# An epic is "incomplete" if any checkbox under it is unchecked.
|
|
72
74
|
echo "Scanning $TASKS_FILE for incomplete epics..."
|
|
73
75
|
|
|
74
76
|
epics=()
|
|
75
77
|
current_epic=""
|
|
78
|
+
has_unchecked=false
|
|
76
79
|
|
|
77
80
|
while IFS= read -r line; do
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
current_epic=""
|
|
81
|
+
# Detect epic heading: ## Epic XXX — Title or ## Epic XXX: Title
|
|
82
|
+
if [[ "$line" =~ ^[#]+\ +Epic\ +(.+)$ ]]; then
|
|
83
|
+
# Save previous epic if it has unchecked tasks
|
|
84
|
+
if [ -n "$current_epic" ] && [ "$has_unchecked" = true ]; then
|
|
85
|
+
epics+=("$current_epic")
|
|
86
|
+
fi
|
|
87
|
+
current_epic="${BASH_REMATCH[1]}"
|
|
88
|
+
current_epic="$(echo "$current_epic" | sed 's/^[#[:space:]]*//' | xargs)"
|
|
89
|
+
has_unchecked=false
|
|
90
|
+
# Detect unchecked checkbox
|
|
91
|
+
elif [[ "$line" =~ ^[[:space:]]*-\ +\[\ \] ]] && [ -n "$current_epic" ]; then
|
|
92
|
+
has_unchecked=true
|
|
86
93
|
fi
|
|
87
94
|
done < "$TASKS_FILE"
|
|
88
95
|
|
|
96
|
+
# Don't forget the last epic
|
|
97
|
+
if [ -n "$current_epic" ] && [ "$has_unchecked" = true ]; then
|
|
98
|
+
epics+=("$current_epic")
|
|
99
|
+
fi
|
|
100
|
+
|
|
89
101
|
if [ ${#epics[@]} -eq 0 ]; then
|
|
90
102
|
echo "✓ All epics are complete! Nothing to do."
|
|
91
103
|
exit 0
|
|
@@ -143,6 +155,14 @@ Instructions:
|
|
|
143
155
|
8. Do NOT work on any other epic"
|
|
144
156
|
|
|
145
157
|
echo "Starting OpenHands (headless)..."
|
|
158
|
+
|
|
159
|
+
# Mount the current directory into the Docker sandbox container so
|
|
160
|
+
# agent file changes persist on the host filesystem.
|
|
161
|
+
# SANDBOX_VOLUMES — mounts $PWD into the container's /workspace (rw)
|
|
162
|
+
# SANDBOX_USER_ID — ensures created files have host-user ownership
|
|
163
|
+
export SANDBOX_VOLUMES="$PWD:/workspace:rw"
|
|
164
|
+
export SANDBOX_USER_ID="${SANDBOX_USER_ID:-$(id -u)}"
|
|
165
|
+
|
|
146
166
|
if $OPENHANDS_CMD --headless --override-with-envs -t "$prompt" 2>&1; then
|
|
147
167
|
echo ""
|
|
148
168
|
echo "✓ [$current/$total] Epic completed: $epic"
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
#
|
|
6
6
|
# Get your OpenRouter API key: https://openrouter.ai/keys
|
|
7
7
|
|
|
8
|
+
# ── LLM Configuration ──────────────────────────────────────────────
|
|
9
|
+
|
|
8
10
|
# Your API key from the LLM provider
|
|
9
11
|
LLM_API_KEY=sk-or-paste-your-api-key-here
|
|
10
12
|
|
|
@@ -14,3 +16,17 @@ LLM_MODEL=openrouter/free
|
|
|
14
16
|
|
|
15
17
|
# OpenRouter API base URL
|
|
16
18
|
LLM_BASE_URL=https://openrouter.ai/api/v1
|
|
19
|
+
|
|
20
|
+
# ── Sandbox / Workspace Persistence ────────────────────────────────
|
|
21
|
+
#
|
|
22
|
+
# The orchestrate.sh script uses openhands --headless, which spawns a
|
|
23
|
+
# Docker sandbox container. Without a volume mount the agent's file
|
|
24
|
+
# changes are lost when the container exits.
|
|
25
|
+
#
|
|
26
|
+
# Setting SANDBOX_VOLUMES mounts the host project directory into the
|
|
27
|
+
# sandbox container's /workspace so all file changes persist on the
|
|
28
|
+
# host filesystem. Both scripts set a sensible default automatically,
|
|
29
|
+
# but you can override it here:
|
|
30
|
+
|
|
31
|
+
# SANDBOX_VOLUMES="$PWD:/workspace:rw"
|
|
32
|
+
# SANDBOX_USER_ID="$(id -u)"
|