diff --git a/CHANGELOG.md b/CHANGELOG.md index 18dda5afe..a5a33fbf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `apm mcp search`, `apm mcp list`, and `apm mcp show` now honour the `MCP_REGISTRY_URL` environment variable (previously hardcoded to the public registry), bringing them in line with `apm install --mcp`. When the variable is set, the discovery commands print a one-line `Registry: ` diagnostic and surface the configured URL in network-error messages so misconfigured enterprise registries are obvious (#813) - `apm install --mcp ... --dry-run` now validates the would-be entry through the same chokepoint the real install uses, so dry-run never previews "success" for an entry `apm install` would reject (empty / whitespace-only / over-128-char / embedded `..` names, invalid transport-conflict combinations) (#810) - `SimpleRegistryClient` now applies a `(connect=10s, read=30s)` timeout on every registry HTTP call, removing the unbounded-hang failure mode when a registry is slow or unreachable. Operators can tune via `MCP_REGISTRY_CONNECT_TIMEOUT` / `MCP_REGISTRY_READ_TIMEOUT` env vars; invalid values silently fall back to defaults (#810) +- `apm init` Next Steps panel now shows install/marketplace/plugin workflows instead of the dead-end `apm run start` reference; no extra files are created besides `apm.yml` (#649) ### Security diff --git a/docs/src/content/docs/getting-started/first-package.md b/docs/src/content/docs/getting-started/first-package.md index a4b44eaaa..dae286961 100644 --- a/docs/src/content/docs/getting-started/first-package.md +++ b/docs/src/content/docs/getting-started/first-package.md @@ -23,15 +23,11 @@ This creates: ``` my-coding-standards/ -├── apm.yml # Package manifest -└── .apm/ - ├── instructions/ # Coding standards (.instructions.md) - ├── prompts/ # Slash commands (.prompt.md) - ├── skills/ # Agent skills (SKILL.md) - ├── agents/ # Personas (.agent.md) - └── hooks/ # Event handlers (.json) +└── apm.yml # Package manifest ``` +> **Note:** By default, `apm init` creates only `apm.yml`. The directory structure below is what you build manually in the following steps. + ## 2. Add an Instruction Create a coding standard that applies to all Python files: diff --git a/docs/src/content/docs/introduction/key-concepts.md b/docs/src/content/docs/introduction/key-concepts.md index 1c125a80a..a66e363f7 100644 --- a/docs/src/content/docs/introduction/key-concepts.md +++ b/docs/src/content/docs/introduction/key-concepts.md @@ -10,30 +10,21 @@ Context components are the configurable tools that deploy proven prompt engineer APM implements Context - the configurable tools that deploy prompt engineering and context engineering techniques to transform unreliable AI interactions into engineered systems. -### Initialize a project with AI-Native structure +### Initialize a project ```bash -apm init my-project # Creates complete Context scaffolding + apm.yml +apm init my-project # Creates apm.yml -- the only file apm init produces ``` ### Generated Project Structure ```yaml my-project/ -├── apm.yml # Project configuration and script definitions -├── SKILL.md # Package meta-guide for AI discovery -└── .apm/ - ├── agents/ # Role-based AI expertise with tool boundaries - │ ├── backend-dev.agent.md # API development specialist - │ └── frontend-dev.agent.md # UI development specialist - ├── instructions/ # Targeted guidance by file type and domain - │ ├── security.instructions.md # applyTo: "auth/**" - │ └── testing.instructions.md # applyTo: "**/*test*" - └── prompts/ # Reusable agent workflows - ├── code-review.prompt.md # Systematic review process - └── feature-spec.prompt.md # Spec-first development +└── apm.yml # Project configuration and dependency manifest ``` +> **Note:** By default, `apm init` creates only `apm.yml`. Add primitives manually or install them with `apm install`. See [Your First Package](../../getting-started/first-package/) for a step-by-step guide. + ### Intelligent Compilation APM automatically compiles your primitives into optimized AGENTS.md files using mathematical optimization: diff --git a/src/apm_cli/commands/init.py b/src/apm_cli/commands/init.py index f1b7cdd57..7582d3d61 100644 --- a/src/apm_cli/commands/init.py +++ b/src/apm_cli/commands/init.py @@ -24,7 +24,6 @@ _validate_project_name, ) - @click.command(help="Initialize a new APM project") @click.argument("project_name", required=False) @click.option( @@ -140,10 +139,10 @@ def init(ctx, project_name, yes, plugin, verbose): ] else: next_steps = [ - "Install a runtime: apm runtime setup copilot", - "Add APM dependencies: apm install /", - "Compile agent context: apm compile", - "Run your first workflow: apm run start", + "Install a skill: apm install github/awesome-copilot/skills/documentation-writer", + "Install a marketplace plugin: apm install frontend-web-dev@awesome-copilot", + "Install a versioned package: apm install microsoft/apm-sample-package#v1.0.0", + "Author your own plugin: apm pack --format plugin", ] try: @@ -157,6 +156,26 @@ def init(ctx, project_name, yes, plugin, verbose): for step in next_steps: click.echo(f" * {step}") + # Footer with links + try: + console = _get_console() + if console: + console.print( + " Docs: https://microsoft.github.io/apm | " + "Star: https://github.com/microsoft/apm", + style="dim", + ) + else: + click.echo( + " Docs: https://microsoft.github.io/apm | " + "Star: https://github.com/microsoft/apm" + ) + except (ImportError, NameError): + click.echo( + " Docs: https://microsoft.github.io/apm | " + "Star: https://github.com/microsoft/apm" + ) + except Exception as e: logger.error(f"Error initializing project: {e}") sys.exit(1) diff --git a/tests/unit/test_init_command.py b/tests/unit/test_init_command.py index cabde1069..80899d66f 100644 --- a/tests/unit/test_init_command.py +++ b/tests/unit/test_init_command.py @@ -36,7 +36,7 @@ def teardown_method(self): os.chdir(str(repo_root)) def test_init_current_directory(self): - """Test initialization in current directory (minimal mode).""" + """Test initialization in current directory.""" with tempfile.TemporaryDirectory() as tmp_dir: os.chdir(tmp_dir) try: @@ -46,7 +46,8 @@ def test_init_current_directory(self): assert result.exit_code == 0 assert "APM project initialized successfully!" in result.output assert Path("apm.yml").exists() - # Minimal mode: no template files created + assert not Path("start.prompt.md").exists() + # No extra template files created assert not Path("hello-world.prompt.md").exists() assert not Path("README.md").exists() assert not Path(".apm").exists() @@ -54,7 +55,7 @@ def test_init_current_directory(self): os.chdir(self.original_dir) # restore CWD before TemporaryDirectory cleanup def test_init_explicit_current_directory(self): - """Test initialization with explicit '.' argument (minimal mode).""" + """Test initialization with explicit '.' argument.""" with tempfile.TemporaryDirectory() as tmp_dir: os.chdir(tmp_dir) try: @@ -64,13 +65,14 @@ def test_init_explicit_current_directory(self): assert result.exit_code == 0 assert "APM project initialized successfully!" in result.output assert Path("apm.yml").exists() - # Minimal mode: no template files created + assert not Path("start.prompt.md").exists() + # No extra template files created assert not Path("hello-world.prompt.md").exists() finally: os.chdir(self.original_dir) # restore CWD before TemporaryDirectory cleanup def test_init_new_directory(self): - """Test initialization in new directory (minimal mode).""" + """Test initialization in new directory.""" with tempfile.TemporaryDirectory() as tmp_dir: os.chdir(tmp_dir) try: @@ -84,7 +86,8 @@ def test_init_new_directory(self): assert project_path.exists() assert project_path.is_dir() assert (project_path / "apm.yml").exists() - # Minimal mode: no template files created + assert not (project_path / "start.prompt.md").exists() + # No extra template files created assert not (project_path / "hello-world.prompt.md").exists() assert not (project_path / "README.md").exists() assert not (project_path / ".apm").exists() @@ -266,7 +269,7 @@ def test_init_existing_project_confirm_uses_click(self): os.chdir(self.original_dir) # restore CWD before TemporaryDirectory cleanup def test_init_validates_project_structure(self): - """Test that init creates minimal project structure.""" + """Test that init creates expected project structure.""" with tempfile.TemporaryDirectory() as tmp_dir: os.chdir(tmp_dir) try: @@ -288,7 +291,9 @@ def test_init_validates_project_structure(self): assert "scripts" in config assert config["scripts"] == {} - # Minimal mode: no template files created + # start.prompt.md NOT created (apm init creates only apm.yml) + assert not (project_path / "start.prompt.md").exists() + # No extra template files created assert not (project_path / "hello-world.prompt.md").exists() assert not (project_path / "README.md").exists() assert not (project_path / ".apm").exists() @@ -341,6 +346,38 @@ def test_init_does_not_create_skill_md(self): finally: os.chdir(self.original_dir) # restore CWD before TemporaryDirectory cleanup + def test_init_next_steps_panel_content(self): + """Test that next steps show install workflows, not apm run start.""" + with tempfile.TemporaryDirectory() as tmp_dir: + os.chdir(tmp_dir) + try: + result = self.runner.invoke(cli, ["init", "--yes"]) + + assert result.exit_code == 0 + # New v5 panel content + assert "apm install" in result.output + assert "apm pack" in result.output + assert "https://microsoft.github.io/apm" in result.output + # Old dead-end content must be gone + assert "apm compile" not in result.output + assert "apm run start" not in result.output + assert "start.prompt.md" not in result.output + finally: + os.chdir(self.original_dir) + + def test_init_created_files_table_no_start_prompt(self): + """Test that Created Files table does NOT list start.prompt.md.""" + with tempfile.TemporaryDirectory() as tmp_dir: + os.chdir(tmp_dir) + try: + result = self.runner.invoke(cli, ["init", "--yes"]) + + assert result.exit_code == 0 + assert "apm.yml" in result.output + assert "start.prompt.md" not in result.output + finally: + os.chdir(self.original_dir) + class TestPluginNameValidation: diff --git a/tests/unit/test_init_plugin.py b/tests/unit/test_init_plugin.py index 60faf4704..093db38bc 100644 --- a/tests/unit/test_init_plugin.py +++ b/tests/unit/test_init_plugin.py @@ -284,6 +284,19 @@ def test_plugin_json_ends_with_newline(self): finally: os.chdir(self.original_dir) + def test_plugin_does_not_create_start_prompt(self): + """start.prompt.md is NOT created in plugin mode.""" + with tempfile.TemporaryDirectory() as tmp_dir: + project_dir = Path(tmp_dir) / "my-plugin" + project_dir.mkdir() + os.chdir(project_dir) + try: + result = self.runner.invoke(cli, ["init", "--plugin", "--yes"]) + assert result.exit_code == 0, result.output + assert not Path("start.prompt.md").exists() + finally: + os.chdir(self.original_dir) + def test_plugin_apm_yml_has_dependencies(self): """apm.yml created with --plugin still has regular dependencies section.""" with tempfile.TemporaryDirectory() as tmp_dir: