diff --git a/.deepwork/jobs/deepwork_rules/hooks/capture_prompt_work_tree.sh b/.deepwork/jobs/deepwork_rules/hooks/capture_prompt_work_tree.sh
index a3323e42..3f2cefad 100755
--- a/.deepwork/jobs/deepwork_rules/hooks/capture_prompt_work_tree.sh
+++ b/.deepwork/jobs/deepwork_rules/hooks/capture_prompt_work_tree.sh
@@ -1,24 +1,27 @@
#!/bin/bash
# capture_prompt_work_tree.sh - Captures the git work tree state at prompt submission
#
-# This script creates a snapshot of the current git state by recording
-# all files that have been modified, added, or deleted. This baseline
-# is used for policies with compare_to: prompt to detect what changed
-# during an agent response (between user prompts).
+# This script creates a snapshot of ALL tracked files at the time the prompt
+# is submitted. This baseline is used for rules with compare_to: prompt and
+# created: mode to detect truly NEW files (not modifications to existing ones).
+#
+# The baseline contains ALL tracked files (not just changed files) so that
+# the rules_check hook can determine which files are genuinely new vs which
+# files existed before and were just modified.
set -e
# Ensure .deepwork directory exists
mkdir -p .deepwork
-# Stage all changes so we can diff against HEAD
-git add -A 2>/dev/null || true
-
-# Save the current state of changed files
-# Using git diff --name-only HEAD to get the list of all changed files
-git diff --name-only HEAD > .deepwork/.last_work_tree 2>/dev/null || true
+# Save ALL tracked files (not just changed files)
+# This is critical for created: mode rules to distinguish between:
+# - Newly created files (not in baseline) -> should trigger created: rules
+# - Modified existing files (in baseline) -> should NOT trigger created: rules
+git ls-files > .deepwork/.last_work_tree 2>/dev/null || true
-# Also include untracked files not yet in the index
+# Also include untracked files that exist at prompt time
+# These are files the user may have created before submitting the prompt
git ls-files --others --exclude-standard >> .deepwork/.last_work_tree 2>/dev/null || true
# Sort and deduplicate
diff --git a/.deepwork/rules/manual-test-created-mode.md b/.deepwork/rules/manual-test-created-mode.md
new file mode 100644
index 00000000..abb6108d
--- /dev/null
+++ b/.deepwork/rules/manual-test-created-mode.md
@@ -0,0 +1,22 @@
+---
+name: "Manual Test: Created Mode"
+created: manual_tests/test_created_mode/*.yml
+compare_to: prompt
+---
+
+# Manual Test: Created Mode (File Creation Trigger)
+
+A new test file was created in the created mode test directory!
+
+**Created:** `{created_files}`
+
+## What to do:
+
+1. Verify the created mode detection is working correctly
+2. Acknowledge with `Manual Test: Created Mode`
+
+## This tests:
+
+The "created" detection mode where rules only fire for newly created files,
+not for modifications to existing files. This is useful for enforcing standards
+on new code specifically.
diff --git a/manual_tests/README.md b/manual_tests/README.md
index 11906456..42569421 100644
--- a/manual_tests/README.md
+++ b/manual_tests/README.md
@@ -36,6 +36,7 @@ Each test has two cases: one where the rule SHOULD fire, and one where it should
| **Multi Safety** | Edit `.py` only | Edit `.py` AND any safety file | Manual Test: Multi Safety |
| **Infinite Block Prompt** | Edit `.py` (always blocks) | Provide `` tag | Manual Test: Infinite Block Prompt |
| **Infinite Block Command** | Edit `.py` (command fails) | Provide `` tag | Manual Test: Infinite Block Command |
+| **Created Mode** | Create NEW `.yml` file | Modify EXISTING `.yml` file | Manual Test: Created Mode |
## Test Results Tracking
@@ -49,6 +50,7 @@ Each test has two cases: one where the rule SHOULD fire, and one where it should
| Multi Safety | ☐ | ☐ |
| Infinite Block Prompt | ☐ | ☐ |
| Infinite Block Command | ☐ | ☐ |
+| Created Mode | ☐ | ☐ |
## Test Folders
@@ -61,6 +63,7 @@ Each test has two cases: one where the rule SHOULD fire, and one where it should
| `test_multi_safety/` | Multiple Safety | Fires unless ANY of the safety files also edited |
| `test_infinite_block_prompt/` | Infinite Block (Prompt) | Always blocks with prompt; only promise can bypass |
| `test_infinite_block_command/` | Infinite Block (Command) | Command always fails; tests if promise skips command |
+| `test_created_mode/` | Created (New Files Only) | Fires ONLY when NEW files are created, not when existing modified |
## Corresponding Rules
@@ -72,3 +75,4 @@ Rules are defined in `.deepwork/rules/`:
- `manual-test-multi-safety.md`
- `manual-test-infinite-block-prompt.md`
- `manual-test-infinite-block-command.md`
+- `manual-test-created-mode.md`
diff --git a/manual_tests/test_created_mode/existing_file.yml b/manual_tests/test_created_mode/existing_file.yml
new file mode 100644
index 00000000..dec0e532
--- /dev/null
+++ b/manual_tests/test_created_mode/existing_file.yml
@@ -0,0 +1 @@
+# This is a modification test
diff --git a/src/deepwork/hooks/rules_check.py b/src/deepwork/hooks/rules_check.py
index 548bcee9..0dae0777 100644
--- a/src/deepwork/hooks/rules_check.py
+++ b/src/deepwork/hooks/rules_check.py
@@ -199,9 +199,16 @@ def get_changed_files_default_tip() -> list[str]:
def get_changed_files_prompt() -> list[str]:
- """Get files changed since prompt was submitted."""
- baseline_path = Path(".deepwork/.last_work_tree")
+ """Get files changed since prompt was submitted.
+
+ Returns ALL files with staged changes (modified, added, deleted).
+ This is used by trigger/safety, set, and pair mode rules to detect
+ file modifications during the agent response.
+ Note: The baseline file (.last_work_tree) is NOT used here - it's only
+ used by get_created_files_prompt() to detect truly NEW files for
+ created: mode rules.
+ """
try:
subprocess.run(["git", "add", "-A"], capture_output=True, check=False)
@@ -214,13 +221,7 @@ def get_changed_files_prompt() -> list[str]:
current_files = set(result.stdout.strip().split("\n")) if result.stdout.strip() else set()
current_files = {f for f in current_files if f}
- if baseline_path.exists():
- baseline_files = set(baseline_path.read_text().strip().split("\n"))
- baseline_files = {f for f in baseline_files if f}
- new_files = current_files - baseline_files
- return sorted(new_files)
- else:
- return sorted(current_files)
+ return sorted(current_files)
except (subprocess.CalledProcessError, OSError):
return []
diff --git a/src/deepwork/standard_jobs/deepwork_rules/hooks/capture_prompt_work_tree.sh b/src/deepwork/standard_jobs/deepwork_rules/hooks/capture_prompt_work_tree.sh
index a3323e42..3f2cefad 100755
--- a/src/deepwork/standard_jobs/deepwork_rules/hooks/capture_prompt_work_tree.sh
+++ b/src/deepwork/standard_jobs/deepwork_rules/hooks/capture_prompt_work_tree.sh
@@ -1,24 +1,27 @@
#!/bin/bash
# capture_prompt_work_tree.sh - Captures the git work tree state at prompt submission
#
-# This script creates a snapshot of the current git state by recording
-# all files that have been modified, added, or deleted. This baseline
-# is used for policies with compare_to: prompt to detect what changed
-# during an agent response (between user prompts).
+# This script creates a snapshot of ALL tracked files at the time the prompt
+# is submitted. This baseline is used for rules with compare_to: prompt and
+# created: mode to detect truly NEW files (not modifications to existing ones).
+#
+# The baseline contains ALL tracked files (not just changed files) so that
+# the rules_check hook can determine which files are genuinely new vs which
+# files existed before and were just modified.
set -e
# Ensure .deepwork directory exists
mkdir -p .deepwork
-# Stage all changes so we can diff against HEAD
-git add -A 2>/dev/null || true
-
-# Save the current state of changed files
-# Using git diff --name-only HEAD to get the list of all changed files
-git diff --name-only HEAD > .deepwork/.last_work_tree 2>/dev/null || true
+# Save ALL tracked files (not just changed files)
+# This is critical for created: mode rules to distinguish between:
+# - Newly created files (not in baseline) -> should trigger created: rules
+# - Modified existing files (in baseline) -> should NOT trigger created: rules
+git ls-files > .deepwork/.last_work_tree 2>/dev/null || true
-# Also include untracked files not yet in the index
+# Also include untracked files that exist at prompt time
+# These are files the user may have created before submitting the prompt
git ls-files --others --exclude-standard >> .deepwork/.last_work_tree 2>/dev/null || true
# Sort and deduplicate