diff --git a/CHANGELOG.md b/CHANGELOG.md
index b6b35e2..090eccb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+### Changed
+- `steplock init` and `steplock init --global` no longer write a default `example-gate` checklist. They create an empty `checklists/` directory
+
### Fixed
- The `steplock init` sample checklist and the `git-push-quality-gate` example matched the literal text `git push`, so `git -C
push` (or any global option between `git` and `push`) skipped the gate. They now match on `command_words`
diff --git a/Installation.md b/Installation.md
index 6756e21..35735bf 100644
--- a/Installation.md
+++ b/Installation.md
@@ -61,7 +61,7 @@ cargo install steplock
## Project setup
-Run inside your repo root. Creates the `.steplock/checklists/` directory tree:
+Run inside your repo root. Creates an empty `.steplock/checklists/` directory and a `.steplock/.gitignore`. No checklist is written:
```sh
steplock init
@@ -177,7 +177,7 @@ Use the full path to the binary (`which steplock`; `~/.cargo/bin/steplock` after
```sh
echo '{"args": {"command": "git push origin main"}, "session_id": "check"}' > /tmp/push.json
hermes hooks test pre_tool_call --for-tool terminal --payload-file /tmp/push.json
- # parsed (Hermes wire shape): {"action": "block", "message": "[example-gate: 1/2] ..."}
+ # parsed (Hermes wire shape): {"action": "block", "message": "[: 1/N] ..."}
```
Hermes runs hooks in the directory where Hermes started, not in the terminal tool's working directory. Global checklists do not depend on this. A project `.steplock/` is found only when Hermes was started inside that project.
@@ -209,7 +209,7 @@ cat .steplock/audit.log
## Global checklists
-Global checklists apply to every project. Create the global steplock directory with a sample checklist:
+Global checklists apply to every project. Create the global steplock directory (with an empty `checklists/`):
```sh
steplock init --global
diff --git a/README.md b/README.md
index 2a73040..f7ada58 100644
--- a/README.md
+++ b/README.md
@@ -127,7 +127,7 @@ Rules:
- A project checklist with the same directory name replaces the global one. An empty `.steplock/checklists//` directory turns that global checklist off for the project.
- Session state and the audit log for global checklists are written to the global directory, not to the project.
-Run `steplock init --global` to create the directory with a sample checklist. Register the hook once in each agent's user-level settings so it runs in every project: `~/.claude/settings.json` for Claude Code, `~/.hermes/config.yaml` for Hermes Agent (see [Installation](Installation.md#hermes-agent)). Every agent reads the same global directory, so one checklist gates all of them.
+Run `steplock init --global` to create the directory with an empty `checklists/`. Register the hook once in each agent's user-level settings so it runs in every project: `~/.claude/settings.json` for Claude Code, `~/.hermes/config.yaml` for Hermes Agent (see [Installation](Installation.md#hermes-agent)). Every agent reads the same global directory, so one checklist gates all of them.
Some agents give hooks a sandboxed `HOME`. Hermes Agent does this in containers and with `TERMINAL_HOME_MODE=profile`, and exports the real home as `HERMES_REAL_HOME`. steplock uses that real home, so the global directory stays the same. To pin it explicitly, set `STEPLOCK_GLOBAL_DIR`.
diff --git a/core/src/bin/main.rs b/core/src/bin/main.rs
index a83f217..e59f16b 100644
--- a/core/src/bin/main.rs
+++ b/core/src/bin/main.rs
@@ -38,7 +38,7 @@ struct Cli {
/// `steplock` subcommands.
#[derive(Debug, Subcommand)]
enum CliCommand {
- /// Create .steplock/checklists/ with a sample checklist in the current directory
+ /// Create an empty .steplock/checklists/ in the current directory
Init {
/// Create checklists/ in the global steplock directory instead
#[arg(long)]
@@ -168,21 +168,12 @@ fn run_hook() {
}
}
-const SAMPLE_CONFIG: &str = r#"on_event = "tool:before"
-on_tool = "bash"
-match_input = "input.command_words.exists(x, x == 'git') && input.command_words.exists(x, x == 'push')"
-reset = "session"
-"#;
-
-const SAMPLE_FLOW: &str = "stateDiagram-v2\n [*] --> tests_pass\n tests_pass --> reviewed\n reviewed --> [*]\n tests_pass : Tests pass locally\n reviewed : Code reviewed\n";
-
/// Create `.steplock/checklists/` and a `.steplock/.gitignore` in `dir`.
-/// Also writes a ready-to-use sample checklist so `git push` is blocked immediately.
fn run_init(dir: &Path) -> io::Result<()> {
init_steplock_dir(&dir.join(".steplock"), true)
}
-/// Create `checklists/` with a sample checklist in `steplock_dir`.
+/// Create an empty `checklists/` in `steplock_dir`. No checklist is written.
/// With `gitignore`, also writes a `.gitignore` for session state and the audit log.
fn init_steplock_dir(steplock_dir: &Path, gitignore: bool) -> io::Result<()> {
let checklists_dir = steplock_dir.join("checklists");
@@ -194,17 +185,8 @@ fn init_steplock_dir(steplock_dir: &Path, gitignore: bool) -> io::Result<()> {
if gitignore {
fs::write(steplock_dir.join(".gitignore"), "sessions/\naudit.log\n")?;
}
- let sample_dir = checklists_dir.join("example-gate");
- fs::create_dir_all(&sample_dir)?;
- fs::write(sample_dir.join("config.toml"), SAMPLE_CONFIG)?;
- fs::write(sample_dir.join("flow.mmd"), SAMPLE_FLOW)?;
println!("steplock: initialized {}", checklists_dir.display());
- println!(
- "A sample checklist was written to {}.",
- sample_dir.display()
- );
- println!("It will block `git push` until two quality checks are acknowledged.");
- println!("Edit config.toml and flow.mmd to customize it, or add more checklists.");
+ println!("Add a checklist as checklists//config.toml and flow.mmd.");
Ok(())
}
diff --git a/core/src/bin/main_tests.rs b/core/src/bin/main_tests.rs
index c8f238c..83b0a4a 100644
--- a/core/src/bin/main_tests.rs
+++ b/core/src/bin/main_tests.rs
@@ -144,25 +144,14 @@ fn init_creates_checklists_dir_and_gitignore() {
}
#[test]
-fn init_scaffolds_sample_checklist() {
- let tmp = TempDir::new().unwrap();
- run_init(tmp.path()).unwrap();
- let sample = tmp.path().join(".steplock/checklists/example-gate");
- assert!(sample.join("config.toml").exists());
- assert!(sample.join("flow.mmd").exists());
- let cfg = fs::read_to_string(sample.join("config.toml")).unwrap();
- assert!(cfg.contains("command_words"));
- let flow = fs::read_to_string(sample.join("flow.mmd")).unwrap();
- assert!(flow.contains("stateDiagram-v2"));
-}
-
-#[test]
-fn init_sample_checklist_is_valid() {
+fn init_writes_no_checklist() {
let tmp = TempDir::new().unwrap();
run_init(tmp.path()).unwrap();
+ let checklists = tmp.path().join(".steplock/checklists");
+ assert_eq!(fs::read_dir(&checklists).unwrap().count(), 0);
let stdin = claude_stdin("git push origin main", "s1");
let resp = run_app(stdin.as_bytes(), tmp.path(), None).unwrap();
- assert!(matches!(resp, polyhook::HookResponse::BlockResponse(_)));
+ assert!(!matches!(resp, polyhook::HookResponse::BlockResponse(_)));
}
#[test]
@@ -477,28 +466,3 @@ fn hermes_session_end_cleans_global_session() {
"on_session_end must clean the global session"
);
}
-
-#[test]
-fn init_sample_blocks_git_push_with_global_options() {
- let tmp = TempDir::new().unwrap();
- run_init(tmp.path()).unwrap();
- for cmd in [
- "git -C /repo push origin main",
- "git push",
- "cd x && git push -q",
- ] {
- let stdin = claude_stdin(cmd, "s-opts");
- let resp = run_app(stdin.as_bytes(), tmp.path(), None).unwrap();
- assert!(
- matches!(resp, polyhook::HookResponse::BlockResponse(_)),
- "sample must block `{cmd}`"
- );
- fs::remove_dir_all(tmp.path().join(".steplock/sessions")).unwrap();
- }
- let stdin = claude_stdin("git status", "s-opts");
- let resp = run_app(stdin.as_bytes(), tmp.path(), None).unwrap();
- assert!(
- !matches!(resp, polyhook::HookResponse::BlockResponse(_)),
- "sample must not block unrelated git commands"
- );
-}
diff --git a/core/tests/cli_tests.rs b/core/tests/cli_tests.rs
index 4effc9f..55cf6f0 100644
--- a/core/tests/cli_tests.rs
+++ b/core/tests/cli_tests.rs
@@ -302,9 +302,12 @@ fn init_global_scaffolds_global_dir() {
let target = global.path().join("steplock");
let output = run_subcommand_with_global(&["init", "--global"], global.path(), &target);
assert!(output.status.success(), "init --global should succeed");
- assert!(
- target.join("checklists/example-gate/config.toml").exists(),
- "sample checklist expected in global dir"
+ let checklists = target.join("checklists");
+ assert!(checklists.is_dir(), "checklists/ expected in global dir");
+ assert_eq!(
+ fs::read_dir(&checklists).unwrap().count(),
+ 0,
+ "init must not write a default checklist"
);
assert!(
!target.join(".gitignore").exists(),