Skip to content

Commit e151b56

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 860c608 commit e151b56

File tree

4 files changed

+118
-10
lines changed

4 files changed

+118
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2828
- `apm install` no longer silently drops skills, agents, and commands when a Claude Code plugin also ships `hooks/*.json`. The package-type detection cascade now classifies plugin-shaped packages as `MARKETPLACE_PLUGIN` (which already maps hooks via the plugin synthesizer) before falling back to the hook-only classification, and emits a default-visibility `[!]` warning when a hook-only classification disagrees with the package's directory contents (#780)
2929
- Preserve custom git ports across protocols: non-default ports on `ssh://` and `https://` dependency URLs (e.g. Bitbucket Datacenter on SSH port 7999, self-hosted GitLab on HTTPS port 8443) are now captured as a first-class `port` field on `DependencyReference` and threaded through all clone URL builders. When the SSH clone fails, the HTTPS fallback reuses the same port instead of silently dropping it (#661, #731)
3030
- Detect port-like first path segment in SCP shorthand (`git@host:7999/path`) and raise an actionable error suggesting the `ssh://` URL form, instead of silently misparsing the port as part of the repository path (#784)
31+
- `apm init` now creates `start.prompt.md` so `apm run start` works out of the box; Next Steps panel no longer references `apm compile` (#649)
3132

3233
## [0.8.12] - 2026-04-19
3334

@@ -36,7 +37,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3637
- `apm install` now automatically discovers and deploys local `.apm/` primitives (skills, instructions, agents, prompts, hooks, commands) to target directories, with local content taking priority over dependencies on collision (#626, #644)
3738
- Deploy primitives from the project root's own `.apm/` directory alongside declared dependencies, so single-package projects no longer need a sub-package stub to install their own content (#715)
3839
- Add `temp-dir` configuration key (`apm config set temp-dir PATH`) to override the system temporary directory, resolving `[WinError 5] Access is denied` in corporate Windows environments (#629)
39-
4040
### Changed
4141

4242
- Refactor `apm install` into a modular engine package (`apm_cli/install/`) with discrete phases (resolve, targets, download, integrate, cleanup, lockfile, finalize, post-deps local) and apply design patterns -- introduce a `DependencySource` Strategy hierarchy with shared `run_integration_template()` Template Method (kills ~300 LOC duplication across local/cached/fresh dep handlers), add `services.py` DI seam to eliminate `_install_mod` indirection, and wrap the pipeline in a typed `InstallService` Application Service consuming a frozen `InstallRequest`. `install/phases/integrate.py` shrinks from 1013 to ~400 LOC; the public `apm install` behaviour and CLI surface are unchanged. Preserves the `#762` cleanup chokepoint and remains backward-compatible (`_install_apm_dependencies` re-export and 55 healthy test patches keep working) (#764)

src/apm_cli/commands/init.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@
2525
_validate_project_name,
2626
)
2727

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

2940
@click.command(help="Initialize a new APM project")
3041
@click.argument("project_name", required=False)
@@ -115,6 +126,16 @@ def init(ctx, project_name, yes, plugin, verbose):
115126
# Create apm.yml (with devDependencies for plugin mode)
116127
_create_minimal_apm_yml(config, plugin=plugin)
117128

129+
# Create start.prompt.md for regular projects (not plugin mode)
130+
start_prompt_created = False
131+
if not plugin:
132+
start_prompt_path = Path(START_PROMPT_FILENAME)
133+
if not start_prompt_path.exists():
134+
start_prompt_path.write_text(START_PROMPT_TEMPLATE, encoding="utf-8")
135+
start_prompt_created = True
136+
else:
137+
logger.progress("start.prompt.md already exists, skipping")
138+
118139
# Create plugin.json for plugin mode
119140
if plugin:
120141
_create_plugin_json(config)
@@ -128,13 +149,17 @@ def init(ctx, project_name, yes, plugin, verbose):
128149
files_data = [
129150
("*", APM_YML_FILENAME, "Project configuration"),
130151
]
152+
if start_prompt_created:
153+
files_data.append(("*", START_PROMPT_FILENAME, "Starter prompt"))
131154
if plugin:
132155
files_data.append(("*", "plugin.json", "Plugin metadata"))
133156
table = _create_files_table(files_data, title="Created Files")
134157
console.print(table)
135158
except (ImportError, NameError):
136159
logger.progress("Created:")
137160
click.echo(" * apm.yml - Project configuration")
161+
if start_prompt_created:
162+
click.echo(" * start.prompt.md - Starter prompt")
138163
if plugin:
139164
click.echo(" * plugin.json - Plugin metadata")
140165

@@ -150,7 +175,7 @@ def init(ctx, project_name, yes, plugin, verbose):
150175
next_steps = [
151176
"Install a runtime: apm runtime setup copilot",
152177
"Add APM dependencies: apm install <owner>/<repo>",
153-
"Compile agent context: apm compile",
178+
"Edit your prompt: start.prompt.md",
154179
"Run your first workflow: apm run start",
155180
]
156181

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)