Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: <url>` 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

Expand Down
10 changes: 3 additions & 7 deletions docs/src/content/docs/getting-started/first-package.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
19 changes: 5 additions & 14 deletions docs/src/content/docs/introduction/key-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Comment thread
sergio-sisternes-epam marked this conversation as resolved.
### Intelligent Compilation

APM automatically compiles your primitives into optimized AGENTS.md files using mathematical optimization:
Expand Down
29 changes: 24 additions & 5 deletions src/apm_cli/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
_validate_project_name,
)


@click.command(help="Initialize a new APM project")
@click.argument("project_name", required=False)
@click.option(
Expand Down Expand Up @@ -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 <owner>/<repo>",
"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:
Expand All @@ -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)
Expand Down
53 changes: 45 additions & 8 deletions tests/unit/test_init_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -46,15 +46,16 @@ 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()
finally:
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:
Expand All @@ -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:
Expand All @@ -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()
Expand Down Expand Up @@ -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:
Expand All @@ -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()
Expand Down Expand Up @@ -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:
Expand Down
13 changes: 13 additions & 0 deletions tests/unit/test_init_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading