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
10 changes: 10 additions & 0 deletions claude-plugin/skills/using-tracedecay/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ code, reading code, tracing calls, estimating blast radius, recalling prior
context — try the matching tracedecay tool BEFORE Grep, Glob, codebase
search, or file reads. You cannot rationalize your way out of this.

## Scope and priority

- SUBAGENT-STOP: if you are a scoped subagent that was handed the exact files,
symbols, or excerpts to act on, do NOT re-run discovery — act on what you
were given. This mandate governs open-ended exploration, not narrow handoffs.
- Priority ladder: explicit user instructions and project rules
(CLAUDE.md / AGENTS.md) win over this skill; this skill wins over the host's
default "just grep it" habit. Never fight a direct instruction to satisfy
the mandate.

## Moment → mandatory action

| The moment you are in | Do this first |
Expand Down
10 changes: 10 additions & 0 deletions codex-plugin/skills/using-tracedecay/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ code, reading code, tracing calls, estimating blast radius, recalling prior
context — try the matching tracedecay tool BEFORE Grep, Glob, codebase
search, or file reads. You cannot rationalize your way out of this.

## Scope and priority

- SUBAGENT-STOP: if you are a scoped subagent that was handed the exact files,
symbols, or excerpts to act on, do NOT re-run discovery — act on what you
were given. This mandate governs open-ended exploration, not narrow handoffs.
- Priority ladder: explicit user instructions and project rules
(CLAUDE.md / AGENTS.md) win over this skill; this skill wins over the host's
default "just grep it" habit. Never fight a direct instruction to satisfy
the mandate.

## Moment → mandatory action

| The moment you are in | Do this first |
Expand Down
10 changes: 10 additions & 0 deletions cursor-plugin/skills/using-tracedecay/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ code, reading code, tracing calls, estimating blast radius, recalling prior
context — try the matching tracedecay tool BEFORE Grep, Glob, codebase
search, or file reads. You cannot rationalize your way out of this.

## Scope and priority

- SUBAGENT-STOP: if you are a scoped subagent that was handed the exact files,
symbols, or excerpts to act on, do NOT re-run discovery — act on what you
were given. This mandate governs open-ended exploration, not narrow handoffs.
- Priority ladder: explicit user instructions and project rules
(CLAUDE.md / AGENTS.md) win over this skill; this skill wins over the host's
default "just grep it" habit. Never fight a direct instruction to satisfy
the mandate.

## Moment → mandatory action

| The moment you are in | Do this first |
Expand Down
44 changes: 26 additions & 18 deletions src/agents/kiro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use super::{
/// Kiro agent.
pub struct KiroIntegration;

const PROMPT_MARKER: &str = "## Prefer tracedecay MCP tools";
const PROMPT_MARKER: &str = "## TraceDecay: mandatory tool routing";
const PROMPT_END_MARKER: &str = "<!-- tracedecay:kiro:end -->";
const KIRO_AGENT_NAME: &str = "tracedecay";
const OWNED_AGENT_DESCRIPTION: &str =
Expand Down Expand Up @@ -615,28 +615,36 @@ fn prompt_rules_text() -> String {

fn prompt_rules_text_without_end_marker() -> String {
format!(
"## Prefer tracedecay MCP tools\n\n\
Before reading source files or scanning the codebase, use the tracedecay MCP tools \
(`tracedecay_context`, `tracedecay_search`, `tracedecay_callers`, `tracedecay_callees`, \
`tracedecay_impact`, `tracedecay_node`, `tracedecay_files`, `tracedecay_affected`). \
They provide semantic results from a pre-built local knowledge graph and are faster \
than broad file reads.\n\n\
"## TraceDecay: mandatory tool routing\n\n\
This project has a live TraceDecay code graph. If there is even a 1% chance a \
tracedecay tool applies, you MUST use it BEFORE Kiro's file reads, codebase scan, \
or `delegate`. This is a mandate, not a preference — you cannot rationalize your \
way out of it.\n\n\
| The moment you are in | Do this first |\n\
|---|---|\n\
| Searching for a symbol or concept | `tracedecay_search` (names) or `tracedecay_context` (concepts) |\n\
| About to read a source file | `tracedecay_outline` -> `tracedecay_body` -> `tracedecay_read` slices |\n\
| \"Who calls X\" / \"what does X call\" / \"trace this\" | `tracedecay_callers` / `tracedecay_callees` |\n\
| About to change code, wondering what breaks | `tracedecay_impact` / `tracedecay_diff_context` / `tracedecay_affected` |\n\
| Project / storage identity question | `tracedecay_active_project` / `tracedecay_storage_status` |\n\
| A prior decision or past conversation is referenced | `tracedecay_message_search` / `tracedecay_lcm_expand_query` |\n\n\
| Red-flag thought | Reality |\n\
|---|---|\n\
| \"Grep is faster for this\" | `tracedecay_search` is one call and pre-ranked. |\n\
| \"I'll just read the whole file\" | `tracedecay_outline` / `tracedecay_body` answer at a fraction of the tokens. |\n\
| \"This is a simple lookup\" | Simple lookups are exactly what the graph is for. |\n\
| \"I already know this codebase\" | The graph is fresher than your memory. Check it. |\n\n\
SUBAGENT-STOP: if you were handed the exact files, symbols, or excerpts to act on, \
do NOT re-run discovery — act on what you were given. Explicit user instructions and \
project rules (CLAUDE.md / AGENTS.md) win over this mandate; the mandate wins over the \
default \"just grep it\" habit. Never fight a direct instruction to satisfy it.\n\n\
Do not use Kiro's `delegate` tool for codebase exploration, architecture mapping, \
call graph work, symbol lookup, or other code research until tracedecay MCP tools \
have been tried. Delegation is still appropriate for long-running execution work \
such as builds, tests, generated reports, or independent implementation tasks.\n\n\
For project/storage identity questions, use `tracedecay_active_project` or \
`tracedecay_storage_status` instead of inferring from marker files or \
direct DB paths.\n\n\
If a code analysis question cannot be fully answered by tracedecay MCP tools, prefer \
built-in MCP tools first. If the user explicitly needs raw store inspection, use the \
resolved graph DB path reported by `tracedecay_storage_status` rather than a hardcoded \
repo path. Use SQL for structural queries that go beyond the MCP tools.\n\n\
For durable project/user facts, prefer `tracedecay_fact_store`, \
`tracedecay_fact_feedback`, and `tracedecay_memory_status` over ad-hoc notes. Use \
`tracedecay_message_search` for active-project transcript recall when prior \
conversation context matters. Do not store secrets, credentials, or unnecessary PII \
in persistent facts.\n\n\
`tracedecay_fact_feedback`, and `tracedecay_memory_status` over ad-hoc notes. Do not \
store secrets, credentials, or unnecessary PII in persistent facts.\n\n\
{cli_fallback}\n\n\
If you discover a gap where an extractor, schema, or tracedecay tool could answer a \
question natively, propose opening an issue at \
Expand Down
17 changes: 12 additions & 5 deletions src/hooks/claude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use super::codex::{
};
use super::post_tool_use::{notify_post_tool_use, CLAUDE_POST_TOOL_USE_SPEC};
use super::steering::{
append_context_recovery_hint, cursor_index_signals_for_root, index_status_line,
session_start_from_compaction,
append_context_recovery_hint, append_tracedecay_bootstrap_context,
cursor_index_signals_for_root, index_status_line, session_start_from_compaction,
};
use super::tool_hints::{decide_hint, HintAgent, ToolHintInput};
use super::{
Expand Down Expand Up @@ -122,13 +122,20 @@ pub async fn hook_claude_session_start() -> i32 {
0
}

/// Builds the lean Claude `SessionStart` context for code workspaces.
async fn claude_session_context_for_event(event_json: &str) -> String {
/// Builds the Claude `SessionStart` context for code workspaces.
///
/// On an initialized project this injects the full `using-tracedecay`
/// adoption contract (the `<EXTREMELY_IMPORTANT>` bootstrap) after the index
/// status line, matching the Cursor and Codex session hooks so Claude's
/// `SessionStart` channel carries the same mandate.
pub async fn claude_session_context_for_event(event_json: &str) -> String {
let parsed = serde_json::from_str::<Value>(event_json).unwrap_or(Value::Null);
match codex_project_root_from_parsed_event(&parsed) {
Some(root) => {
let (staleness, _) = cursor_index_signals_for_root(&root).await;
index_status_line(true, staleness.as_deref())
let mut context = index_status_line(true, staleness.as_deref());
append_tracedecay_bootstrap_context(&mut context);
context
}
None if event_cwd_from_parsed(&parsed)
.as_deref()
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ mod steering;
pub mod tool_hints;

pub use claude::{
evaluate_hook_decision, hook_claude_post_tool_use, hook_claude_session_start,
hook_pre_tool_use, hook_prompt_submit, hook_stop,
claude_session_context_for_event, evaluate_hook_decision, hook_claude_post_tool_use,
hook_claude_session_start, hook_pre_tool_use, hook_prompt_submit, hook_stop,
};
pub use codex::{
codex_additional_context_json, codex_apply_patch_rel_paths, codex_project_root_from_event,
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/steering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub const CURSOR_PLUGIN_SKILLS: &[&str] = &[
const TRACEDECAY_BOOTSTRAP_SKILL: &str =
include_str!("../../cursor-plugin/skills/using-tracedecay/SKILL.md");

fn append_tracedecay_bootstrap_context(s: &mut String) {
pub(super) fn append_tracedecay_bootstrap_context(s: &mut String) {
s.push_str(
"<EXTREMELY_IMPORTANT>\n\
This project has TraceDecay code-graph support. Below is the full `tracedecay:using-tracedecay` \
Expand Down
14 changes: 7 additions & 7 deletions tests/agent_suite/kiro_agent_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ fn test_install_creates_global_mcp_steering_agent_and_default() {
"global Kiro tracedecay.md should exist"
);
let steering = std::fs::read_to_string(&steering_path).unwrap();
assert!(steering.contains("## Prefer tracedecay MCP tools"));
assert!(steering.contains("## TraceDecay: mandatory tool routing"));
assert!(steering.contains("delegate"));

let agent_path = home.join(".kiro/agents/tracedecay.json");
Expand Down Expand Up @@ -333,18 +333,18 @@ fn test_install_and_uninstall_preserve_existing_steering_content() {
let user_steering = std::fs::read_to_string(&user_steering_path).unwrap();
assert!(user_steering.contains("## Existing Kiro guidance"));
assert!(user_steering.contains("Keep this user-authored guidance."));
assert!(!user_steering.contains("## Prefer tracedecay MCP tools"));
assert!(!user_steering.contains("## TraceDecay: mandatory tool routing"));

let tracedecay_steering_path = home.join(".kiro/steering/tracedecay.md");
let installed = std::fs::read_to_string(&tracedecay_steering_path).unwrap();
assert!(installed.contains("## Prefer tracedecay MCP tools"));
assert!(installed.contains("## TraceDecay: mandatory tool routing"));

KiroIntegration.uninstall(&ctx).unwrap();

let uninstalled = std::fs::read_to_string(&user_steering_path).unwrap();
assert!(uninstalled.contains("## Existing Kiro guidance"));
assert!(uninstalled.contains("Keep this user-authored guidance."));
assert!(!uninstalled.contains("## Prefer tracedecay MCP tools"));
assert!(!uninstalled.contains("## TraceDecay: mandatory tool routing"));
assert!(!tracedecay_steering_path.exists());
}

Expand All @@ -368,7 +368,7 @@ fn test_uninstall_preserves_user_steering_after_tracedecay_block() {

let uninstalled = std::fs::read_to_string(&steering_path).unwrap();
assert!(uninstalled.contains("User guidance appended after setup without a new heading."));
assert!(!uninstalled.contains("## Prefer tracedecay MCP tools"));
assert!(!uninstalled.contains("## TraceDecay: mandatory tool routing"));
}

#[test]
Expand Down Expand Up @@ -401,7 +401,7 @@ fn test_uninstall_removes_tracedecay_and_preserves_other_mcp_servers() {
);
let steering =
std::fs::read_to_string(home.join(".kiro/steering/tracedecay.md")).unwrap_or_default();
assert!(!steering.contains("## Prefer tracedecay MCP tools"));
assert!(!steering.contains("## TraceDecay: mandatory tool routing"));
}

#[test]
Expand Down Expand Up @@ -525,7 +525,7 @@ fn test_healthcheck_fails_when_steering_lacks_owned_end_marker() {
let steering_path = home.join(".kiro/steering/tracedecay.md");
std::fs::write(
&steering_path,
"## Prefer tracedecay MCP tools\n\nEdited tracedecay guidance without ownership marker.\n",
"## TraceDecay: mandatory tool routing\n\nEdited tracedecay guidance without ownership marker.\n",
)
.unwrap();

Expand Down
3 changes: 2 additions & 1 deletion tests/agent_suite/prompt_rules_parity_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::common::{EnvVarGuard, PROCESS_ENV_LOCK};

const STANDARD_MARKER: &str = "## Prefer tracedecay MCP tools";
const CLAUDE_MARKER: &str = "## MANDATORY: No Explore Agents When Tracedecay Is Available";
const KIRO_MARKER: &str = "## TraceDecay: mandatory tool routing";
const KIRO_END_MARKER: &str = "<!-- tracedecay:kiro:end -->";

struct HostCase {
Expand Down Expand Up @@ -61,7 +62,7 @@ fn hosts() -> Vec<HostCase> {
HostCase {
id: "kiro",
rules_path: |home| home.join(".kiro/steering/tracedecay.md"),
marker: STANDARD_MARKER,
marker: KIRO_MARKER,
stale_block_tail: "\n\n<!-- tracedecay:kiro:end -->",
},
]
Expand Down
58 changes: 56 additions & 2 deletions tests/hooks_lsp_suite/hooks_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use crate::common::{EnvVarGuard, GLOBAL_DB_ENV, GLOBAL_DB_ENV_LOCK};
use std::path::Path;
use tracedecay::config::USER_DATA_DIR_ENV;
use tracedecay::hooks::{
build_cursor_session_context, codex_additional_context_json, codex_apply_patch_rel_paths,
codex_project_root_from_event, codex_subagent_start_log_line,
build_cursor_session_context, claude_session_context_for_event, codex_additional_context_json,
codex_apply_patch_rel_paths, codex_project_root_from_event, codex_subagent_start_log_line,
codex_user_prompt_submit_context_for_event, codex_workspace_status_from_event,
cursor_branch_switch_target, cursor_project_root_from_event, cursor_session_start_json,
cursor_shell_command_targets_project, cursor_shell_sync_plan,
Expand Down Expand Up @@ -828,6 +828,60 @@ fn test_build_codex_session_context_carries_full_steering() {
assert!(uninit.contains("tracedecay_message_search"));
}

#[tokio::test]
async fn test_claude_session_context_injects_bootstrap_when_initialized() {
// On an initialized project Claude's SessionStart additionalContext must
// carry the full using-tracedecay adoption contract, not just the index
// status line (matching Cursor and Codex).
let dir = tempfile::tempdir().unwrap();
std::fs::create_dir_all(dir.path().join(".tracedecay")).unwrap();
std::fs::write(dir.path().join(".tracedecay/tracedecay.db"), "").unwrap();
let event = serde_json::json!({
"hook_event_name": "SessionStart",
"cwd": dir.path().to_str().unwrap(),
})
.to_string();

let context = claude_session_context_for_event(&event).await;
assert!(
context.contains("tracedecay index status: "),
"initialized Claude context keeps the index status line: {context}"
);
assert!(
context.contains("<EXTREMELY_IMPORTANT>"),
"initialized Claude context must inject the bootstrap contract: {context}"
);
assert!(context.contains("tracedecay:using-tracedecay"));
assert!(context.contains("Grep is faster for this"));
assert!(
context.contains("SUBAGENT-STOP"),
"bootstrap must carry the scoped-subagent guard: {context}"
);
// The additionalContext channel wraps it as SessionStart context.
let json = codex_additional_context_json("SessionStart", &context);
assert!(json.contains("<EXTREMELY_IMPORTANT>"));
}

#[tokio::test]
async fn test_claude_session_context_omits_bootstrap_for_unindexed_project() {
// A project-like workspace without an index gets the init nudge, not the
// full contract.
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("Cargo.toml"), "[package]\n").unwrap();
let event = serde_json::json!({
"hook_event_name": "SessionStart",
"cwd": dir.path().to_str().unwrap(),
})
.to_string();

let context = claude_session_context_for_event(&event).await;
assert!(context.contains("tracedecay init"));
assert!(
!context.contains("<EXTREMELY_IMPORTANT>"),
"unindexed workspaces should not inject the full contract: {context}"
);
}

#[test]
fn test_build_codex_session_context_for_unindexed_project_suggests_init() {
let context = tracedecay::hooks::build_codex_session_context_for_workspace(
Expand Down
Loading