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
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,3 @@
/result
/result-*
/.claude/settings.local.json
# Per-worktree agent scratch (PR bodies, logs, notes); see the start-quest skill.
/.scratch/
1 change: 0 additions & 1 deletion assets/skills/start.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ Claim it as `quest guide` describes: `quest branch` names the branch and its bas
Delete a claim you cannot finish (remote branch and worktree).

Implement the quest until it is complete, or some blocker is hit, then create a PR against the base.
Keep scratch files (PR body, logs, notes) in the worktree's gitignored `.scratch/`.
Never write to or clean up a directory other agents share, such as a session scratchpad.

When done, explain the result in a few lines and summarize any issues encountered.
Expand Down
12 changes: 5 additions & 7 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ quest init
```

This writes a Quest stub for each shipped skill under `.claude/skills/`, links
`.agents/skills/` to the same tree when needed, adds `/.scratch/` to
`.gitignore`, creates `quest/README.md` when missing, and appends one line to
your root `AGENTS.md` or `CLAUDE.md` telling agents to run `quest guide` when
work mentions a quest. That line starts with `Quests: `; reword the rest freely,
`.agents/skills/` to the same tree when needed, creates `quest/README.md` when
missing, and appends one line to your root `AGENTS.md` or `CLAUDE.md` telling
agents to run `quest guide` when work mentions a quest. That line starts with `Quests: `; reword the rest freely,
since init and uninstall recognize it by that prefix.

Init refuses to overwrite an existing same-named skill that is not a Quest stub.
Expand Down Expand Up @@ -84,6 +83,5 @@ GitHub issues, then run:
quest uninstall
```

Uninstall removes only Quest stubs that still match, the reference line, and the
`/.scratch/` ignore entry. It never deletes your quest tree, and completed plans
remain in Git history. Then remove the pin.
Uninstall removes only Quest stubs that still match and the reference line. It
never deletes your quest tree, and completed plans remain in Git history. Then remove the pin.
29 changes: 8 additions & 21 deletions src/setup.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! `quest init` and `quest uninstall`: install skill stubs and reverse it.
//!
//! Init touches only stubs, one reference line, `/.scratch/` in `.gitignore`, and
//! an empty `quest/README.md` when missing. Uninstall never deletes the quest tree.
//! Init touches only stubs, one reference line, and an empty `quest/README.md`
//! when missing. Uninstall never deletes the quest tree.

use std::fs;
use std::path::{Path, PathBuf};
Expand All @@ -17,8 +17,6 @@ const REFERENCE_MARKER: &str = "Quests: ";
/// Appended to the root agent instructions when Quest is installed.
pub const REFERENCE_LINE: &str = "Quests: when work mentions a quest, run `quest guide` and follow it.";

const SCRATCH_IGNORE: &str = "/.scratch/";

/// The skill directories Claude Code and Codex read. One holds the stubs and
/// the other links to it.
const SKILL_DIRS: [&str; 2] = [".claude/skills", ".agents/skills"];
Expand Down Expand Up @@ -65,16 +63,11 @@ pub fn init(root: &Path) -> Result<Vec<PathBuf>> {
changes.push(readme.into());
}

let ignore = Path::new(".gitignore");
if append_line(root, ignore, SCRATCH_IGNORE, SCRATCH_IGNORE, false)? {
changes.push(ignore.into());
}

let instructions = ["AGENTS.md", "CLAUDE.md"]
.into_iter()
.find(|name| root.join(name).is_file())
.unwrap_or("AGENTS.md");
if append_line(root, Path::new(instructions), REFERENCE_MARKER, REFERENCE_LINE, true)? {
if append_line(root, Path::new(instructions), REFERENCE_MARKER, REFERENCE_LINE)? {
changes.push(instructions.into());
}
Ok(changes)
Expand Down Expand Up @@ -113,12 +106,8 @@ pub fn uninstall(root: &Path) -> Result<Vec<PathBuf>> {
}
}

for (path, marker) in [
(".gitignore", SCRATCH_IGNORE),
("AGENTS.md", REFERENCE_MARKER),
("CLAUDE.md", REFERENCE_MARKER),
] {
if remove_line(root, Path::new(path), marker)? {
for path in ["AGENTS.md", "CLAUDE.md"] {
if remove_line(root, Path::new(path), REFERENCE_MARKER)? {
changes.push(path.into());
}
}
Expand Down Expand Up @@ -188,8 +177,8 @@ fn write(root: &Path, path: &Path, content: &str) -> Result<()> {
}

/// Append `line` unless a line already starts with `marker`, creating the file
/// if missing. `paragraph` separates it from existing content with a blank line.
fn append_line(root: &Path, path: &Path, marker: &str, line: &str, paragraph: bool) -> Result<bool> {
/// if missing, separated from existing content by a blank line.
fn append_line(root: &Path, path: &Path, marker: &str, line: &str) -> Result<bool> {
let mut content = if root.join(path).is_file() {
read(root, path)?
} else {
Expand All @@ -202,9 +191,7 @@ fn append_line(root: &Path, path: &Path, marker: &str, line: &str, paragraph: bo
if !content.ends_with('\n') {
content.push('\n');
}
if paragraph {
content.push('\n');
}
content.push('\n');
}
content.push_str(line);
content.push('\n');
Expand Down
5 changes: 1 addition & 4 deletions tests/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,8 @@ fn init_creates_layout_and_is_idempotent() {
assert!(dir.path().join(".claude/skills/quest-start/SKILL.md").is_file());
assert!(dir.path().join(".agents/skills/quest-start/SKILL.md").is_file());
assert!(dir.path().join("quest/README.md").is_file());
assert!(dir.path().join(".gitignore").is_file());
assert!(!dir.path().join(".gitignore").exists());
assert!(dir.path().join("AGENTS.md").is_file());
let ignore = std::fs::read_to_string(dir.path().join(".gitignore")).unwrap();
assert!(ignore.lines().any(|line| line == "/.scratch/"));
let agents = std::fs::read_to_string(dir.path().join("AGENTS.md")).unwrap();
assert!(agents.contains(quest::setup::REFERENCE_LINE));

Expand Down Expand Up @@ -80,7 +78,6 @@ fn uninstall_round_trip_preserves_user_content() {
let agents = std::fs::read_to_string(dir.path().join("AGENTS.md")).unwrap();
assert_eq!(agents, "# Repo\n\nUser rule.\n");
assert!(dir.path().join("quest/m0/plan.md").is_file());
assert!(!dir.path().join(".gitignore").exists());
assert!(!dir.path().join(".claude").exists());
assert!(!dir.path().join(".agents").exists());
}
Expand Down
Loading