Skip to content

Commit 346d0b3

Browse files
committed
docs(cli): expand repo command specs and health-check contract
- Added Repo Init & Instructions sections in specs/cli-spec.md: - repo init defaults, editor flow, agent-driven init, post-init symlink creation - repo instructions create/link behavior, JSON output notes, conflict handling - repo check validations and health references; health agent diagnostics - Formalized Devcontainer Health Check Entrypoint in specs/devcontainer-setup.md - Script path, invocation, exit codes, JSON output, recommended checks - Fixed typos and normalized naming in .agents/tasks/2025/08/27-1105-repo-cli-new-commands\n - Use "repo instructions link"; corrected spelling and --devenv - Added TODO notes in .agents/codebase-insights.txt for follow-ups and naming No runtime code changes; documentation and task notes only.
1 parent 9b27101 commit 346d0b3

12 files changed

+2775
-1
lines changed

.agents/codebase-insights.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,11 @@
1919
- Set EXTRAS="nix,direnv,cachix" with flexible delimiter parsing
2020
- Uses Rake tasks in Rakefile.extras for dependency resolution
2121

22+
## TODOs — CLI Repo Commands Spec (WIP)
23+
- Clarify final command names: typos in draft use "insturctions"; spec adopts `aw repo instructions create` and `aw repo instructions link`. Consider alias `aw repo create-agent-instruction-symlinks` for backwards compatibility with docs.
24+
- Define exact editor discovery order and env/config keys reused by `aw repo init` when prompting for project description.
25+
- Specify JSON schemas for `repo init`, `instructions create`, `instructions link`, `repo check`, and `health` in a central place (currently summarized inline in cli-spec.md).
26+
- Enumerate supported agent sets and mapping to link targets (align with the reference Ruby example). Ensure Windows notes (Git Bash/WSL symlinks) are captured in docs.
27+
- Devcontainer health check: reference the procedure in `specs/devcontainer-setup.md` and `specs/devcontainer-design.md`; formalize a single entrypoint the CLI will call.
28+
- Config layering: ensure `supported-agents` can be set in system/user/project scopes and overridden by CLI; document keys and env vars.
29+
- Decide on fallback when multiple instruction sources exist and no `source-file` is provided (current behavior: error; alternative: prompt interactively).
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
Read the work-in-progress specs in the specs folder.
2+
3+
We'll be expanding the CLI spec with some new commands:
4+
5+
* aw repo init [--vcs=git|hg|etc] [--devenv=no|nix|spack|bazel|etc] [--devcontainer=yes|no] [--direnv=yes|no] [--task-runner=just|make|etc] [--supported-agents=all|codex|claude|etc] [optional-project-description-string]
6+
7+
When the project description is not provided. We launch an editor in the standard configurable way and the user enters the project description there
8+
The aw tool then launches a local agent to initialize the repo. The standard command-line and configuration settings control which agent is used and how it is launched.
9+
10+
git is the default version control system.
11+
just is the default task runner.
12+
13+
--devenv defaults to nix. "none" is a valid alias of "no"
14+
--devcontainer is enabled by default
15+
--direnv is enabled by default
16+
--supported-agents is set to all by default.
17+
18+
The selected command-line options and project description are combined into a suitable task prompt that is handed off to the agent in conversational mode.
19+
The prompt suggests to the agent to collect any additional details for initializing the repo from the user. The agent is asked to suggest the use of specific testing frameworks and linters. The instructions tell the agent that if the user approves these choices, at the end of the conversation the agent should create an AGENTS.md file providing the instructions for running the tests and lints using the selected task runner. The init command completes by creating symlinks for all supported agents (see the `aw repo instructions link` command below which executes the same step in an already initialized repo)
20+
21+
* aw repo instructions create
22+
23+
Uses a similar process to the init command above, but the agent is additionally prompted to review the repo before collecting information from the user.
24+
25+
* aw repo instructions link [--supported-agents=all|codex|etc] [source-file]
26+
27+
This command will have behavior similar to the following ruby program:
28+
29+
```
30+
#!/usr/bin/env ruby
31+
# frozen_string_literal: true
32+
#
33+
# sync_agent_links.rb
34+
#
35+
# Create relative symlinks from various AI agent "rules" files to AGENTS.md,
36+
# and add them to git. Supports selecting which agents to target.
37+
#
38+
# Examples:
39+
# ruby sync_agent_links.rb # all agents (default)
40+
# ruby sync_agent_links.rb --agents=copilot,claude # only those agents
41+
# ruby sync_agent_links.rb --force # overwrite conflicts
42+
# ruby sync_agent_links.rb --dry-run # preview actions
43+
#
44+
# Notes:
45+
# - Run from anywhere inside the repo (it will cd to repo root).
46+
# - On Windows, create symlinks via Git Bash or WSL; native symlinks may require admin.
47+
48+
require "optparse"
49+
require "pathname"
50+
require "fileutils"
51+
require "open3"
52+
53+
# ---------- Agent → link targets (relative to repo root) ----------
54+
AGENT_LINKS = {
55+
# Generic catch-alls commonly read by multiple tools
56+
"generic" => ["AGENT.md", "README.agent.md", ".rules"],
57+
58+
# Specific tools
59+
"copilot" => [".github/copilot-instructions.md",
60+
".github/instructions/AGENTS.instructions.md"],
61+
"claude" => ["CLAUDE.md"],
62+
"gemini" => ["GEMINI.md"],
63+
"cursor" => [".cursor/rules/AGENTS.mdc", ".cursorrules"],
64+
"windsurf" => [".windsurf/rules/AGENTS.md", ".windsurfrules"],
65+
"zed" => [".rules"], # Zed reads several names; .rules is its native one
66+
"cline" => [".clinerules"],
67+
"roo" => [".roorules"],
68+
"jetbrains" => [".aiassistant/rules/AGENTS.md", ".junie/guidelines.md"],
69+
"openhands" => [".openhands/microagents/repo.md"],
70+
}.freeze
71+
72+
VALID_AGENT_NAMES = AGENT_LINKS.keys.sort.freeze
73+
74+
# ---------- CLI options ----------
75+
opts = {
76+
force: false,
77+
dry_run: false,
78+
agents: nil, # nil means "all"
79+
}
80+
81+
parser = OptionParser.new do |o|
82+
o.banner = "Usage: ruby sync_agent_links.rb [--agents=a,b,c] [--force] [--dry-run]"
83+
84+
o.on("--agents=LIST", "Comma-separated agent keys (default: all).",
85+
"Valid: #{VALID_AGENT_NAMES.join(', ')}") do |list|
86+
opts[:agents] = list.split(",").map(&:strip).reject(&:empty?)
87+
end
88+
89+
o.on("--force", "Overwrite existing files/symlinks") { opts[:force] = true }
90+
o.on("--dry-run", "Show actions without changing anything") { opts[:dry_run] = true }
91+
o.on("-h", "--help", "Show this help") { puts o; exit 0 }
92+
end
93+
94+
begin
95+
parser.parse!
96+
rescue OptionParser::ParseError => e
97+
warn e.message
98+
warn parser
99+
exit 1
100+
end
101+
102+
# ---------- Helpers ----------
103+
def run!(cmd_ary, dry:)
104+
if dry
105+
puts "[dry-run] #{cmd_ary.join(' ')}"
106+
true
107+
else
108+
system(*cmd_ary)
109+
end
110+
end
111+
112+
def inside_git_repo?
113+
system("git", "rev-parse", "--is-inside-work-tree", out: File::NULL, err: File::NULL)
114+
end
115+
116+
def git_root
117+
out, st = Open3.capture2("git", "rev-parse", "--show-toplevel")
118+
st.success? ? out.strip : nil
119+
end
120+
121+
def relpath(from_dir_abs, target_abs)
122+
Pathname.new(target_abs).relative_path_from(Pathname.new(from_dir_abs)).to_s
123+
end
124+
125+
# ---------- Preconditions ----------
126+
unless inside_git_repo?
127+
warn "Error: must run inside a git repository."
128+
exit 1
129+
end
130+
131+
repo_root = git_root
132+
if repo_root.nil? || repo_root.empty?
133+
warn "Error: unable to determine git repo root."
134+
exit 1
135+
end
136+
137+
agents_file = File.join(repo_root, "AGENTS.md")
138+
unless File.file?(agents_file)
139+
warn "Error: #{agents_file} not found. Create it first."
140+
exit 1
141+
end
142+
143+
Dir.chdir(repo_root)
144+
145+
# Determine which agents to act on
146+
selected_agents =
147+
if opts[:agents].nil? || opts[:agents].empty? || opts[:agents].include?("all")
148+
VALID_AGENT_NAMES
149+
else
150+
unknown = opts[:agents] - VALID_AGENT_NAMES
151+
unless unknown.empty?
152+
warn "Unknown agent(s): #{unknown.join(', ')}"
153+
warn "Valid agents: #{VALID_AGENT_NAMES.join(', ')}"
154+
exit 1
155+
end
156+
opts[:agents]
157+
end
158+
159+
# Build link target list
160+
links = selected_agents.flat_map { |k| AGENT_LINKS[k] }.uniq
161+
162+
puts "Repo: #{repo_root}"
163+
puts "AGENTS.md: #{agents_file}"
164+
puts "Agents: #{selected_agents.join(', ')}"
165+
puts "Links to create: #{links.size}"
166+
puts
167+
168+
created = 0
169+
skipped = 0
170+
git_added = 0
171+
172+
links.each do |link_rel|
173+
link_path = File.join(repo_root, link_rel)
174+
link_dir = File.dirname(link_path)
175+
176+
# Ensure parent directory exists
177+
unless Dir.exist?(link_dir)
178+
if opts[:dry_run]
179+
puts "[dry-run] mkdir -p #{link_dir}"
180+
else
181+
FileUtils.mkdir_p(link_dir)
182+
end
183+
end
184+
185+
# Compute relative target path from link's directory to AGENTS.md
186+
rel_to_agents = relpath(link_dir, agents_file)
187+
188+
# Handle existing files/symlinks
189+
if File.symlink?(link_path)
190+
current = File.readlink(link_path) rescue nil
191+
if current == rel_to_agents && !opts[:force]
192+
puts "OK (already linked): #{link_rel} -> #{current}"
193+
skipped += 1
194+
next
195+
end
196+
197+
if opts[:force]
198+
puts "Rewriting symlink: #{link_rel}"
199+
if opts[:dry_run]
200+
puts "[dry-run] rm -f #{link_rel}"
201+
else
202+
FileUtils.rm_f(link_path)
203+
end
204+
else
205+
puts "Skip (different symlink; use --force): #{link_rel} -> #{current}"
206+
skipped += 1
207+
next
208+
end
209+
elsif File.exist?(link_path)
210+
if opts[:force]
211+
puts "Replacing file: #{link_rel}"
212+
if opts[:dry_run]
213+
puts "[dry-run] rm -f #{link_rel}"
214+
else
215+
FileUtils.rm_f(link_path)
216+
end
217+
else
218+
puts "Skip (exists and not a symlink): #{link_rel}"
219+
skipped += 1
220+
next
221+
end
222+
end
223+
224+
# Create symlink (relative)
225+
if opts[:dry_run]
226+
puts "[dry-run] ln -s #{rel_to_agents} #{link_rel}"
227+
else
228+
File.symlink(rel_to_agents, link_path)
229+
end
230+
created += 1
231+
232+
# git add -f
233+
if run!(["git", "add", "-f", link_rel], dry: opts[:dry_run])
234+
git_added += 1
235+
end
236+
end
237+
238+
puts
239+
puts "Done."
240+
puts "Created: #{created} Skipped: #{skipped} Git-added: #{git_added}"
241+
puts "(dry-run: nothing actually changed)" if opts[:dry_run]
242+
243+
```
244+
245+
When a source-file is not provided, the command will look for an existing instructions file (for one of the supported agents).
246+
If there is a single existing instructions file, it would be assumed to be the source file.
247+
248+
* aw repo check [--supported-agents=...]
249+
250+
This command will perform various checks:
251+
252+
* Are there instruction files? Print information about the missing ones if there is disagreement with the supported-agents configuration value (remember, this configuration value can be supplied on the command-line here, but it may also appear in a configuration file within the repo, in the user home folder, on the system, etc).
253+
254+
* Is there a devcontainer setup? Does its own health check procedure pass (please document that such procedure will be available in the devcontainer spec)
255+
256+
* aw health [--supported-agents=...]
257+
258+
Performs diagnostic health checks for the presence and login status of the configured agentic tools.
259+
260+
Don't write any code. We are currently working on the spec.
261+
262+
Create a precise copy of these instructions in the .agents/task folder as the `agent-task` tool usually does.

0 commit comments

Comments
 (0)