Skip to content

Commit 7f83cea

Browse files
nhortonclaude
andcommitted
fix: align job creation directory with MCP server project root
make_new_job.sh used git rev-parse --show-toplevel to decide where to create jobs, while the MCP server uses listRoots for discovery. These diverge when the session root differs from the git root (nested repos, cd changes, non-git parent dirs), causing newly created jobs to be invisible to get_workflows/start_workflow. Add project_root to ActiveStepInfo so agents always know the MCP server's canonical root. Change make_new_job.sh to require --project-root instead of inferring it from git. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent fc8e1bf commit 7f83cea

8 files changed

Lines changed: 61 additions & 19 deletions

File tree

specs/deepwork/jobs/JOBS-REQ-001-mcp-workflow-tools.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ The DeepWork MCP server exposes workflow tools to AI agents via the Model Contex
4242
7. The tool MUST create a new workflow session via `StateManager`.
4343
8. The tool MUST resolve input values for the first step from provided `inputs` and previous step outputs.
4444
9. The tool MUST mark the first step as started with resolved input values.
45-
10. The response MUST contain a `begin_step` (`ActiveStepInfo`) with: `session_id`, `step_id`, `job_dir`, `step_expected_outputs`, `step_inputs`, `step_instructions`, `common_job_info`.
45+
10. The response MUST contain a `begin_step` (`ActiveStepInfo`) with: `session_id`, `step_id`, `project_root`, `job_dir`, `step_expected_outputs`, `step_inputs`, `step_instructions`, `common_job_info`. The `project_root` field MUST be the absolute path the MCP server uses for job discovery and `.deepwork/` operations.
4646
11. The response MUST contain a `stack` field and an `important_note` field instructing the agent to clarify ambiguous requests.
4747
12. Each expected output MUST include `name`, `type`, `description`, `required`, and `syntax_for_finished_step_tool`.
4848

src/deepwork/jobs/mcp/schemas.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,11 @@ class ActiveStepInfo(BaseModel):
228228
)
229229
)
230230
step_id: str = Field(description="Name of the current step")
231+
project_root: str = Field(
232+
description="Absolute path to the MCP server's project root. "
233+
"Use this as the base directory for .deepwork/ operations "
234+
"(e.g. creating jobs at [project_root]/.deepwork/jobs/)."
235+
)
231236
job_dir: str = Field(
232237
description="Absolute path to the job directory. Templates, scripts, "
233238
"and other files referenced in step instructions live here."

src/deepwork/jobs/mcp/tools.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ def _build_active_step_info(
388388
return ActiveStepInfo(
389389
session_id=session_id,
390390
step_id=step.name,
391+
project_root=str(self.project_root),
391392
job_dir=str(job.job_dir),
392393
step_expected_outputs=step_outputs,
393394
step_inputs=step_inputs,

src/deepwork/standard_jobs/deepwork_jobs/job.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,17 +348,17 @@ workflows:
348348
349349
Only after you have complete understanding, create the job directory and `job.yml` file.
350350
351-
**Note**: `[job_dir]` refers to the `job_dir` path returned in the workflow response when this workflow was started. It points to the directory containing this job's definition, scripts, and templates.
351+
**Note**: `[job_dir]` refers to the `job_dir` path returned in the workflow response when this workflow was started. It points to the directory containing this job's definition, scripts, and templates. `[project_root]` refers to the `project_root` path from the same response — the MCP server's canonical root directory.
352352
353353
**First, create the directory structure** using the `make_new_job.sh` script:
354354
355355
```bash
356-
[job_dir]/make_new_job.sh [job_name]
356+
[job_dir]/make_new_job.sh --project-root [project_root] [job_name]
357357
```
358358
359-
**Then create the job.yml file** at `.deepwork/jobs/[job_name]/job.yml`
359+
**Then create the job.yml file** at `[project_root]/.deepwork/jobs/[job_name]/job.yml`
360360
361-
(Where `[job_name]` is the name of the NEW job you're creating, e.g., `competitive_research`. Replace `[job_dir]` with the actual `job_dir` path from the workflow response.)
361+
(Where `[job_name]` is the name of the NEW job you're creating, e.g., `competitive_research`. Replace `[job_dir]` and `[project_root]` with the actual paths from the workflow response.)
362362
363363
**Template reference**: See `[job_dir]/templates/job.yml.template` for the standard structure.
364364

src/deepwork/standard_jobs/deepwork_jobs/make_new_job.sh

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,24 @@
22
#
33
# make_new_job.sh - Create directory structure for a new DeepWork job
44
#
5-
# Usage: ./make_new_job.sh <job_name>
5+
# Usage: ./make_new_job.sh --project-root DIR <job_name>
66
#
77
# Input:
8-
# job_name - Lowercase name using only letters, numbers, and underscores.
9-
# Must start with a letter.
8+
# --project-root DIR - Required. The MCP server's project root directory.
9+
# Jobs are created at DIR/.deepwork/jobs/<job_name>/.
10+
# job_name - Lowercase name using only letters, numbers, and
11+
# underscores. Must start with a letter.
1012
#
1113
# Output:
12-
# Creates .deepwork/jobs/<job_name>/ at the git repository root with
14+
# Creates .deepwork/jobs/<job_name>/ under the specified project root with
1315
# subdirectories: hooks/, templates/, scripts/, plus AGENTS.md
1416
# and optionally .deepreview (if template.deepreview exists alongside
1517
# this script).
1618
#
1719
# Exit codes:
1820
# 0 - Success
1921
# 1 - Usage error (missing args, invalid job name, job already exists,
20-
# not a git repository)
22+
# missing --project-root)
2123
#
2224

2325
set -euo pipefail
@@ -54,28 +56,52 @@ main() {
5456
local script_dir
5557
script_dir="$(cd "$(dirname "$0")" && pwd)"
5658

59+
# Parse --project-root flag
60+
local project_root=""
61+
while [[ $# -gt 0 ]]; do
62+
case "$1" in
63+
--project-root)
64+
if [[ $# -lt 2 ]]; then
65+
error "--project-root requires a directory argument"
66+
fi
67+
project_root="$2"
68+
shift 2
69+
;;
70+
-*)
71+
error "Unknown option: $1"
72+
;;
73+
*)
74+
break
75+
;;
76+
esac
77+
done
78+
79+
if [[ -z "$project_root" ]]; then
80+
error "--project-root is required. Pass the project_root from the MCP workflow response."
81+
fi
82+
83+
if [[ ! -d "$project_root" ]]; then
84+
error "Project root directory does not exist: $project_root"
85+
fi
86+
5787
if [[ $# -lt 1 ]]; then
58-
echo "Usage: $0 <job_name>"
88+
echo "Usage: $0 --project-root DIR <job_name>"
5989
echo ""
6090
echo "Creates the directory structure for a new DeepWork job."
6191
echo ""
6292
echo "Arguments:"
63-
echo " job_name Name of the job (lowercase, underscores allowed)"
93+
echo " --project-root DIR Project root directory (required)"
94+
echo " job_name Name of the job (lowercase, underscores allowed)"
6495
echo ""
6596
echo "Example:"
66-
echo " $0 competitive_research"
97+
echo " $0 --project-root /path/to/project competitive_research"
6798
exit 1
6899
fi
69100

70101
local job_name="$1"
71102
validate_job_name "$job_name"
72103

73-
# Determine the base path by anchoring to the git repository root
74-
local repo_root
75-
if ! repo_root="$(git rev-parse --show-toplevel 2>/dev/null)"; then
76-
error "Not inside a git repository. Run this from within a git repo."
77-
fi
78-
local base_path="${repo_root}/.deepwork/jobs"
104+
local base_path="${project_root}/.deepwork/jobs"
79105
mkdir -p "$base_path"
80106

81107
local job_path="${base_path}/${job_name}"

tests/unit/jobs/mcp/test_schemas.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ def test_basic_step_info(self) -> None:
217217
step_info = ActiveStepInfo(
218218
session_id="abc123",
219219
step_id="step1",
220+
project_root="/tmp/project",
220221
job_dir="/tmp/test_job",
221222
step_expected_outputs=expected,
222223
step_inputs=step_inputs,
@@ -226,6 +227,7 @@ def test_basic_step_info(self) -> None:
226227

227228
assert step_info.session_id == "abc123"
228229
assert step_info.step_id == "step1"
230+
assert step_info.project_root == "/tmp/project"
229231
assert step_info.job_dir == "/tmp/test_job"
230232
assert len(step_info.step_expected_outputs) == 1
231233
assert step_info.step_expected_outputs[0].name == "output.md"
@@ -242,6 +244,7 @@ def test_default_step_inputs(self) -> None:
242244
step_info = ActiveStepInfo(
243245
session_id="abc123",
244246
step_id="step1",
247+
project_root="/tmp/project",
245248
job_dir="/tmp/test_job",
246249
step_expected_outputs=[
247250
ExpectedOutput(
@@ -262,6 +265,7 @@ def test_default_common_job_info(self) -> None:
262265
step_info = ActiveStepInfo(
263266
session_id="abc123",
264267
step_id="step1",
268+
project_root="/tmp/project",
265269
job_dir="/tmp/test_job",
266270
step_expected_outputs=[
267271
ExpectedOutput(
@@ -287,6 +291,7 @@ def test_basic_response(self) -> None:
287291
begin_step=ActiveStepInfo(
288292
session_id="abc123",
289293
step_id="step1",
294+
project_root="/tmp/project",
290295
job_dir="/tmp/test_job",
291296
step_expected_outputs=[
292297
ExpectedOutput(
@@ -328,6 +333,7 @@ def test_next_step_status(self) -> None:
328333
begin_step=ActiveStepInfo(
329334
session_id="abc123",
330335
step_id="step2",
336+
project_root="/tmp/project",
331337
job_dir="/tmp/test_job",
332338
step_expected_outputs=[
333339
ExpectedOutput(

tests/unit/jobs/mcp/test_server.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ async def test_start_workflow_delegates_to_tools(self, tmp_path: Path) -> None:
267267
begin_step=ActiveStepInfo(
268268
session_id="sess-1",
269269
step_id="step1",
270+
project_root=str(tmp_path),
270271
job_dir="/tmp/jobs/myjob",
271272
step_instructions="Do the thing",
272273
step_expected_outputs=[],
@@ -304,6 +305,7 @@ async def test_start_workflow_with_inputs_and_agent_id(self, tmp_path: Path) ->
304305
begin_step=ActiveStepInfo(
305306
session_id="s",
306307
step_id="s1",
308+
project_root=str(tmp_path),
307309
job_dir="/tmp/jobs/j",
308310
step_instructions="go",
309311
step_expected_outputs=[],
@@ -418,6 +420,7 @@ async def test_go_to_step_delegates(self, tmp_path: Path) -> None:
418420
begin_step=ActiveStepInfo(
419421
session_id="s",
420422
step_id="step1",
423+
project_root=str(tmp_path),
421424
job_dir="/tmp/jobs/j",
422425
step_instructions="Redo",
423426
step_expected_outputs=[],

tests/unit/jobs/mcp/test_tools.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ async def test_creates_session_and_returns_first_step(self, tools: WorkflowTools
219219

220220
assert step.session_id == SESSION_ID
221221
assert step.step_id == "step1"
222+
assert step.project_root == str(tools.project_root)
222223
assert "Do the first step" in step.step_instructions
223224
assert len(step.step_expected_outputs) == 1
224225
assert step.step_expected_outputs[0].name == "output1"

0 commit comments

Comments
 (0)