Skip to content

Commit 3dc3dde

Browse files
Sergio SisternesCopilot
andcommitted
fix(init): create start.prompt.md and fix next steps panel (#603)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b56920e commit 3dc3dde

4 files changed

Lines changed: 118 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010

1111
### Fixed
1212

13+
- `apm init` now creates `start.prompt.md` so `apm run start` works out of the box; Next Steps panel no longer references `apm compile` (#603)
1314
- Propagate headers and environment variables through OpenCode MCP adapter with defensive copies to prevent mutation (#622)
1415
### Changed
1516

src/apm_cli/commands/init.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@
2424
_validate_plugin_name,
2525
)
2626

27+
START_PROMPT_FILENAME = "start.prompt.md"
28+
29+
START_PROMPT_TEMPLATE = """\
30+
---
31+
name: start
32+
---
33+
You are a helpful assistant working on this project.
34+
35+
Help me understand the codebase and suggest improvements.
36+
"""
37+
2738

2839
@click.command(help="Initialize a new APM project")
2940
@click.argument("project_name", required=False)
@@ -106,6 +117,16 @@ def init(ctx, project_name, yes, plugin, verbose):
106117
# Create apm.yml (with devDependencies for plugin mode)
107118
_create_minimal_apm_yml(config, plugin=plugin)
108119

120+
# Create start.prompt.md for regular projects (not plugin mode)
121+
start_prompt_created = False
122+
if not plugin:
123+
start_prompt_path = Path(START_PROMPT_FILENAME)
124+
if not start_prompt_path.exists():
125+
start_prompt_path.write_text(START_PROMPT_TEMPLATE, encoding="utf-8")
126+
start_prompt_created = True
127+
else:
128+
logger.progress("start.prompt.md already exists, skipping")
129+
109130
# Create plugin.json for plugin mode
110131
if plugin:
111132
_create_plugin_json(config)
@@ -119,13 +140,17 @@ def init(ctx, project_name, yes, plugin, verbose):
119140
files_data = [
120141
("*", APM_YML_FILENAME, "Project configuration"),
121142
]
143+
if start_prompt_created:
144+
files_data.append(("*", START_PROMPT_FILENAME, "Starter prompt"))
122145
if plugin:
123146
files_data.append(("*", "plugin.json", "Plugin metadata"))
124147
table = _create_files_table(files_data, title="Created Files")
125148
console.print(table)
126149
except (ImportError, NameError):
127150
logger.progress("Created:")
128151
click.echo(" * apm.yml - Project configuration")
152+
if start_prompt_created:
153+
click.echo(" * start.prompt.md - Starter prompt")
129154
if plugin:
130155
click.echo(" * plugin.json - Plugin metadata")
131156

@@ -141,7 +166,7 @@ def init(ctx, project_name, yes, plugin, verbose):
141166
next_steps = [
142167
"Install a runtime: apm runtime setup copilot",
143168
"Add APM dependencies: apm install <owner>/<repo>",
144-
"Compile agent context: apm compile",
169+
"Edit your prompt: start.prompt.md",
145170
"Run your first workflow: apm run start",
146171
]
147172

tests/unit/test_init_command.py

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def teardown_method(self):
3636
os.chdir(str(repo_root))
3737

3838
def test_init_current_directory(self):
39-
"""Test initialization in current directory (minimal mode)."""
39+
"""Test initialization in current directory."""
4040
with tempfile.TemporaryDirectory() as tmp_dir:
4141
os.chdir(tmp_dir)
4242
try:
@@ -46,15 +46,16 @@ def test_init_current_directory(self):
4646
assert result.exit_code == 0
4747
assert "APM project initialized successfully!" in result.output
4848
assert Path("apm.yml").exists()
49-
# Minimal mode: no template files created
49+
assert Path("start.prompt.md").exists()
50+
# No extra template files created
5051
assert not Path("hello-world.prompt.md").exists()
5152
assert not Path("README.md").exists()
5253
assert not Path(".apm").exists()
5354
finally:
5455
os.chdir(self.original_dir) # restore CWD before TemporaryDirectory cleanup
5556

5657
def test_init_explicit_current_directory(self):
57-
"""Test initialization with explicit '.' argument (minimal mode)."""
58+
"""Test initialization with explicit '.' argument."""
5859
with tempfile.TemporaryDirectory() as tmp_dir:
5960
os.chdir(tmp_dir)
6061
try:
@@ -64,13 +65,14 @@ def test_init_explicit_current_directory(self):
6465
assert result.exit_code == 0
6566
assert "APM project initialized successfully!" in result.output
6667
assert Path("apm.yml").exists()
67-
# Minimal mode: no template files created
68+
assert Path("start.prompt.md").exists()
69+
# No extra template files created
6870
assert not Path("hello-world.prompt.md").exists()
6971
finally:
7072
os.chdir(self.original_dir) # restore CWD before TemporaryDirectory cleanup
7173

7274
def test_init_new_directory(self):
73-
"""Test initialization in new directory (minimal mode)."""
75+
"""Test initialization in new directory."""
7476
with tempfile.TemporaryDirectory() as tmp_dir:
7577
os.chdir(tmp_dir)
7678
try:
@@ -84,7 +86,8 @@ def test_init_new_directory(self):
8486
assert project_path.exists()
8587
assert project_path.is_dir()
8688
assert (project_path / "apm.yml").exists()
87-
# Minimal mode: no template files created
89+
assert (project_path / "start.prompt.md").exists()
90+
# No extra template files created
8891
assert not (project_path / "hello-world.prompt.md").exists()
8992
assert not (project_path / "README.md").exists()
9093
assert not (project_path / ".apm").exists()
@@ -221,7 +224,7 @@ def test_init_existing_project_interactive_cancel(self):
221224
os.chdir(self.original_dir) # restore CWD before TemporaryDirectory cleanup
222225

223226
def test_init_validates_project_structure(self):
224-
"""Test that init creates minimal project structure."""
227+
"""Test that init creates expected project structure."""
225228
with tempfile.TemporaryDirectory() as tmp_dir:
226229
os.chdir(tmp_dir)
227230
try:
@@ -243,7 +246,9 @@ def test_init_validates_project_structure(self):
243246
assert "scripts" in config
244247
assert config["scripts"] == {}
245248

246-
# Minimal mode: no template files created
249+
# start.prompt.md created
250+
assert (project_path / "start.prompt.md").exists()
251+
# No extra template files created
247252
assert not (project_path / "hello-world.prompt.md").exists()
248253
assert not (project_path / "README.md").exists()
249254
assert not (project_path / ".apm").exists()
@@ -296,6 +301,71 @@ def test_init_does_not_create_skill_md(self):
296301
finally:
297302
os.chdir(self.original_dir) # restore CWD before TemporaryDirectory cleanup
298303

304+
def test_init_creates_start_prompt_md(self):
305+
"""Test that init creates start.prompt.md with correct content."""
306+
with tempfile.TemporaryDirectory() as tmp_dir:
307+
os.chdir(tmp_dir)
308+
try:
309+
result = self.runner.invoke(cli, ["init", "--yes"])
310+
311+
assert result.exit_code == 0
312+
prompt_path = Path("start.prompt.md")
313+
assert prompt_path.exists()
314+
315+
content = prompt_path.read_text(encoding="utf-8")
316+
assert "---" in content
317+
assert "name: start" in content
318+
assert "You are a helpful assistant" in content
319+
finally:
320+
os.chdir(self.original_dir)
321+
322+
def test_init_does_not_overwrite_existing_start_prompt(self):
323+
"""Test that init preserves existing start.prompt.md (brownfield)."""
324+
with tempfile.TemporaryDirectory() as tmp_dir:
325+
os.chdir(tmp_dir)
326+
try:
327+
# Create existing start.prompt.md with custom content
328+
Path("start.prompt.md").write_text(
329+
"---\nname: start\n---\nMy custom prompt\n", encoding="utf-8"
330+
)
331+
332+
result = self.runner.invoke(cli, ["init", "--yes"])
333+
334+
assert result.exit_code == 0
335+
content = Path("start.prompt.md").read_text(encoding="utf-8")
336+
assert "My custom prompt" in content
337+
assert "start.prompt.md already exists" in result.output
338+
finally:
339+
os.chdir(self.original_dir)
340+
341+
def test_init_next_steps_no_compile(self):
342+
"""Test that next steps do not reference apm compile."""
343+
with tempfile.TemporaryDirectory() as tmp_dir:
344+
os.chdir(tmp_dir)
345+
try:
346+
result = self.runner.invoke(cli, ["init", "--yes"])
347+
348+
assert result.exit_code == 0
349+
assert "apm compile" not in result.output
350+
assert "Edit your prompt" in result.output
351+
assert "start.prompt.md" in result.output
352+
assert "apm run start" in result.output
353+
finally:
354+
os.chdir(self.original_dir)
355+
356+
def test_init_created_files_table_includes_start_prompt(self):
357+
"""Test that Created Files table lists start.prompt.md."""
358+
with tempfile.TemporaryDirectory() as tmp_dir:
359+
os.chdir(tmp_dir)
360+
try:
361+
result = self.runner.invoke(cli, ["init", "--yes"])
362+
363+
assert result.exit_code == 0
364+
# start.prompt.md appears in the Created Files table
365+
assert "start.prompt.md" in result.output
366+
finally:
367+
os.chdir(self.original_dir)
368+
299369

300370

301371
class TestPluginNameValidation:

tests/unit/test_init_plugin.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,19 @@ def test_plugin_json_ends_with_newline(self):
284284
finally:
285285
os.chdir(self.original_dir)
286286

287+
def test_plugin_does_not_create_start_prompt(self):
288+
"""start.prompt.md is NOT created in plugin mode."""
289+
with tempfile.TemporaryDirectory() as tmp_dir:
290+
project_dir = Path(tmp_dir) / "my-plugin"
291+
project_dir.mkdir()
292+
os.chdir(project_dir)
293+
try:
294+
result = self.runner.invoke(cli, ["init", "--plugin", "--yes"])
295+
assert result.exit_code == 0, result.output
296+
assert not Path("start.prompt.md").exists()
297+
finally:
298+
os.chdir(self.original_dir)
299+
287300
def test_plugin_apm_yml_has_dependencies(self):
288301
"""apm.yml created with --plugin still has regular dependencies section."""
289302
with tempfile.TemporaryDirectory() as tmp_dir:

0 commit comments

Comments
 (0)