Skip to content

Commit 786b7a0

Browse files
committed
Persist .plain/AGENTS.md via plain dev
1 parent fdb9e80 commit 786b7a0

File tree

5 files changed

+75
-41
lines changed

5 files changed

+75
-41
lines changed

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Example workflow:
6969
## Coding style
7070

7171
- Don't include args and returns in docstrings if they are already type annotated.
72+
- CLI command docstrings should be concise, informative, no punctuation at the end.
7273

7374
## Verifying changes
7475

plain-dev/plain/dev/core.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ def run(self) -> int:
136136
)
137137

138138
self.symlink_plain_src()
139+
self.generate_agents_md()
139140
self.modify_hosts_file()
140141

141142
click.secho("→ Running preflight checks... ", dim=True, nl=False)
@@ -209,6 +210,28 @@ def symlink_plain_src(self) -> None:
209210
if plain_path.exists() and not symlink_path.exists():
210211
symlink_path.symlink_to(plain_path)
211212

213+
def generate_agents_md(self) -> None:
214+
"""Generate .plain/AGENTS.md from installed packages with AGENTS.md files."""
215+
try:
216+
result = subprocess.run(
217+
[sys.executable, "-m", "plain", "agent", "md", "--save"],
218+
check=False,
219+
capture_output=True,
220+
text=True,
221+
)
222+
if result.returncode != 0 and result.stderr:
223+
click.secho(
224+
f"Warning: Failed to generate .plain/AGENTS.md: {result.stderr}",
225+
fg="yellow",
226+
err=True,
227+
)
228+
except Exception as e:
229+
click.secho(
230+
f"Warning: Failed to generate .plain/AGENTS.md: {e}",
231+
fg="yellow",
232+
err=True,
233+
)
234+
212235
def modify_hosts_file(self) -> None:
213236
"""Modify the hosts file to map the custom domain to 127.0.0.1."""
214237
entry_identifier = "# Added by plain"

plain-observer/plain/observer/AGENTS.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@
22

33
- Send a request and record traces: `plain agent request /some/path --user 1 --header "Observer: persist"`
44
- Find traces by request ID: `plain observer traces --request-id abc-123-def`
5-
- Diagnose trace: `plain observer diagnose [TRACE_ID]`
65
- Output raw trace data: `plain observer trace [TRACE_ID] --json`

plain/plain/cli/agent/__init__.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,10 @@
55
from .request import request
66

77

8-
@click.group("agent", invoke_without_command=True)
9-
@click.pass_context
10-
def agent(ctx: click.Context) -> None:
8+
@click.group("agent")
9+
def agent() -> None:
1110
"""Tools for coding agents"""
12-
if ctx.invoked_subcommand is None:
13-
# If no subcommand provided, show all AGENTS.md files
14-
ctx.invoke(md, show_all=True, show_list=False, package="")
11+
pass
1512

1613

1714
# Add commands to the group

plain/plain/cli/agent/md.py

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
import click
88

9+
from plain.runtime import PLAIN_TEMP_PATH
10+
911
from ..output import iterate_markdown
1012

1113

@@ -20,11 +22,13 @@ def _get_packages_with_agents() -> dict[str, Path]:
2022
# Check core plain package (namespace package)
2123
plain_spec = importlib.util.find_spec("plain")
2224
if plain_spec and plain_spec.submodule_search_locations:
23-
# For namespace packages, use the first search location
24-
plain_path = Path(plain_spec.submodule_search_locations[0])
25-
agents_path = plain_path / "AGENTS.md"
26-
if agents_path.exists():
27-
agents_files["plain"] = agents_path
25+
# For namespace packages, check all search locations
26+
for location in plain_spec.submodule_search_locations:
27+
plain_path = Path(location)
28+
agents_path = plain_path / "AGENTS.md"
29+
if agents_path.exists():
30+
agents_files["plain"] = agents_path
31+
break # Use the first one found
2832

2933
# Check other plain.* subpackages
3034
if hasattr(plain, "__path__"):
@@ -49,44 +53,54 @@ def _get_packages_with_agents() -> dict[str, Path]:
4953

5054

5155
@click.command("md")
52-
@click.argument("package", default="", required=False)
53-
@click.option(
54-
"--all",
55-
"show_all",
56-
is_flag=True,
57-
help="Show AGENTS.md for all packages that have them",
58-
)
5956
@click.option(
60-
"--list",
61-
"show_list",
62-
is_flag=True,
63-
help="List packages with AGENTS.md files",
57+
"--save",
58+
default=None,
59+
is_flag=False,
60+
flag_value="PLAIN_TEMP_PATH",
61+
help="Save combined AGENTS.md from all packages to file (default: .plain/AGENTS.md)",
6462
)
65-
def md(package: str, show_all: bool, show_list: bool) -> None:
66-
"""Show AGENTS.md for a package"""
63+
def md(save: str | None) -> None:
64+
"""AGENTS.md from installed Plain packages"""
6765

6866
agents_files = _get_packages_with_agents()
6967

70-
if show_list:
71-
for pkg in sorted(agents_files.keys()):
72-
click.echo(f"- {pkg}")
73-
68+
if not agents_files:
7469
return
7570

76-
if show_all:
71+
# Handle --save flag
72+
if save:
73+
# Use PLAIN_TEMP_PATH if flag was used without value
74+
if save == "PLAIN_TEMP_PATH":
75+
save_path = PLAIN_TEMP_PATH / "AGENTS.md"
76+
else:
77+
save_path = Path(save)
78+
79+
# Check if we need to regenerate
80+
if save_path.exists():
81+
output_mtime = save_path.stat().st_mtime
82+
# Check if any source file is newer
83+
needs_regen = any(
84+
path.stat().st_mtime > output_mtime for path in agents_files.values()
85+
)
86+
if not needs_regen:
87+
return
88+
89+
# Ensure parent directory exists
90+
save_path.parent.mkdir(parents=True, exist_ok=True)
91+
92+
# Generate combined file
93+
with save_path.open("w") as f:
94+
for pkg_name in sorted(agents_files.keys()):
95+
content = agents_files[pkg_name].read_text()
96+
f.write(content)
97+
if not content.endswith("\n"):
98+
f.write("\n")
99+
f.write("\n")
100+
else:
101+
# Display to console
77102
for pkg in sorted(agents_files.keys()):
78103
agents_path = agents_files[pkg]
79104
for line in iterate_markdown(agents_path.read_text()):
80105
click.echo(line, nl=False)
81106
print()
82-
83-
return
84-
85-
if not package:
86-
raise click.UsageError(
87-
"Package name or --all required. Use --list to see available packages."
88-
)
89-
90-
agents_path = agents_files[package]
91-
for line in iterate_markdown(agents_path.read_text()):
92-
click.echo(line, nl=False)

0 commit comments

Comments
 (0)