Skip to content

Commit 6cc152e

Browse files
Merge pull request #249 from ScriptedAlchemy/feat/adoption-contract-injection
feat(hooks): inject the using-tracedecay mandate contract on Claude and Kiro
2 parents 21ba759 + 9890962 commit 6cc152e

10 files changed

Lines changed: 136 additions & 36 deletions

File tree

claude-plugin/skills/using-tracedecay/SKILL.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ code, reading code, tracing calls, estimating blast radius, recalling prior
1212
context — try the matching tracedecay tool BEFORE Grep, Glob, codebase
1313
search, or file reads. You cannot rationalize your way out of this.
1414

15+
## Scope and priority
16+
17+
- SUBAGENT-STOP: if you are a scoped subagent that was handed the exact files,
18+
symbols, or excerpts to act on, do NOT re-run discovery — act on what you
19+
were given. This mandate governs open-ended exploration, not narrow handoffs.
20+
- Priority ladder: explicit user instructions and project rules
21+
(CLAUDE.md / AGENTS.md) win over this skill; this skill wins over the host's
22+
default "just grep it" habit. Never fight a direct instruction to satisfy
23+
the mandate.
24+
1525
## Moment → mandatory action
1626

1727
| The moment you are in | Do this first |

codex-plugin/skills/using-tracedecay/SKILL.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ code, reading code, tracing calls, estimating blast radius, recalling prior
1212
context — try the matching tracedecay tool BEFORE Grep, Glob, codebase
1313
search, or file reads. You cannot rationalize your way out of this.
1414

15+
## Scope and priority
16+
17+
- SUBAGENT-STOP: if you are a scoped subagent that was handed the exact files,
18+
symbols, or excerpts to act on, do NOT re-run discovery — act on what you
19+
were given. This mandate governs open-ended exploration, not narrow handoffs.
20+
- Priority ladder: explicit user instructions and project rules
21+
(CLAUDE.md / AGENTS.md) win over this skill; this skill wins over the host's
22+
default "just grep it" habit. Never fight a direct instruction to satisfy
23+
the mandate.
24+
1525
## Moment → mandatory action
1626

1727
| The moment you are in | Do this first |

cursor-plugin/skills/using-tracedecay/SKILL.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ code, reading code, tracing calls, estimating blast radius, recalling prior
1212
context — try the matching tracedecay tool BEFORE Grep, Glob, codebase
1313
search, or file reads. You cannot rationalize your way out of this.
1414

15+
## Scope and priority
16+
17+
- SUBAGENT-STOP: if you are a scoped subagent that was handed the exact files,
18+
symbols, or excerpts to act on, do NOT re-run discovery — act on what you
19+
were given. This mandate governs open-ended exploration, not narrow handoffs.
20+
- Priority ladder: explicit user instructions and project rules
21+
(CLAUDE.md / AGENTS.md) win over this skill; this skill wins over the host's
22+
default "just grep it" habit. Never fight a direct instruction to satisfy
23+
the mandate.
24+
1525
## Moment → mandatory action
1626

1727
| The moment you are in | Do this first |

src/agents/kiro.rs

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use super::{
3030
/// Kiro agent.
3131
pub struct KiroIntegration;
3232

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

616616
fn prompt_rules_text_without_end_marker() -> String {
617617
format!(
618-
"## Prefer tracedecay MCP tools\n\n\
619-
Before reading source files or scanning the codebase, use the tracedecay MCP tools \
620-
(`tracedecay_context`, `tracedecay_search`, `tracedecay_callers`, `tracedecay_callees`, \
621-
`tracedecay_impact`, `tracedecay_node`, `tracedecay_files`, `tracedecay_affected`). \
622-
They provide semantic results from a pre-built local knowledge graph and are faster \
623-
than broad file reads.\n\n\
618+
"## TraceDecay: mandatory tool routing\n\n\
619+
This project has a live TraceDecay code graph. If there is even a 1% chance a \
620+
tracedecay tool applies, you MUST use it BEFORE Kiro's file reads, codebase scan, \
621+
or `delegate`. This is a mandate, not a preference — you cannot rationalize your \
622+
way out of it.\n\n\
623+
| The moment you are in | Do this first |\n\
624+
|---|---|\n\
625+
| Searching for a symbol or concept | `tracedecay_search` (names) or `tracedecay_context` (concepts) |\n\
626+
| About to read a source file | `tracedecay_outline` -> `tracedecay_body` -> `tracedecay_read` slices |\n\
627+
| \"Who calls X\" / \"what does X call\" / \"trace this\" | `tracedecay_callers` / `tracedecay_callees` |\n\
628+
| About to change code, wondering what breaks | `tracedecay_impact` / `tracedecay_diff_context` / `tracedecay_affected` |\n\
629+
| Project / storage identity question | `tracedecay_active_project` / `tracedecay_storage_status` |\n\
630+
| A prior decision or past conversation is referenced | `tracedecay_message_search` / `tracedecay_lcm_expand_query` |\n\n\
631+
| Red-flag thought | Reality |\n\
632+
|---|---|\n\
633+
| \"Grep is faster for this\" | `tracedecay_search` is one call and pre-ranked. |\n\
634+
| \"I'll just read the whole file\" | `tracedecay_outline` / `tracedecay_body` answer at a fraction of the tokens. |\n\
635+
| \"This is a simple lookup\" | Simple lookups are exactly what the graph is for. |\n\
636+
| \"I already know this codebase\" | The graph is fresher than your memory. Check it. |\n\n\
637+
SUBAGENT-STOP: if you were handed the exact files, symbols, or excerpts to act on, \
638+
do NOT re-run discovery — act on what you were given. Explicit user instructions and \
639+
project rules (CLAUDE.md / AGENTS.md) win over this mandate; the mandate wins over the \
640+
default \"just grep it\" habit. Never fight a direct instruction to satisfy it.\n\n\
624641
Do not use Kiro's `delegate` tool for codebase exploration, architecture mapping, \
625642
call graph work, symbol lookup, or other code research until tracedecay MCP tools \
626643
have been tried. Delegation is still appropriate for long-running execution work \
627644
such as builds, tests, generated reports, or independent implementation tasks.\n\n\
628-
For project/storage identity questions, use `tracedecay_active_project` or \
629-
`tracedecay_storage_status` instead of inferring from marker files or \
630-
direct DB paths.\n\n\
631-
If a code analysis question cannot be fully answered by tracedecay MCP tools, prefer \
632-
built-in MCP tools first. If the user explicitly needs raw store inspection, use the \
633-
resolved graph DB path reported by `tracedecay_storage_status` rather than a hardcoded \
634-
repo path. Use SQL for structural queries that go beyond the MCP tools.\n\n\
635645
For durable project/user facts, prefer `tracedecay_fact_store`, \
636-
`tracedecay_fact_feedback`, and `tracedecay_memory_status` over ad-hoc notes. Use \
637-
`tracedecay_message_search` for active-project transcript recall when prior \
638-
conversation context matters. Do not store secrets, credentials, or unnecessary PII \
639-
in persistent facts.\n\n\
646+
`tracedecay_fact_feedback`, and `tracedecay_memory_status` over ad-hoc notes. Do not \
647+
store secrets, credentials, or unnecessary PII in persistent facts.\n\n\
640648
{cli_fallback}\n\n\
641649
If you discover a gap where an extractor, schema, or tracedecay tool could answer a \
642650
question natively, propose opening an issue at \

src/hooks/claude.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use super::codex::{
1111
};
1212
use super::post_tool_use::{notify_post_tool_use, CLAUDE_POST_TOOL_USE_SPEC};
1313
use super::steering::{
14-
append_context_recovery_hint, cursor_index_signals_for_root, index_status_line,
15-
session_start_from_compaction,
14+
append_context_recovery_hint, append_tracedecay_bootstrap_context,
15+
cursor_index_signals_for_root, index_status_line, session_start_from_compaction,
1616
};
1717
use super::tool_hints::{decide_hint, HintAgent, ToolHintInput};
1818
use super::{
@@ -122,13 +122,20 @@ pub async fn hook_claude_session_start() -> i32 {
122122
0
123123
}
124124

125-
/// Builds the lean Claude `SessionStart` context for code workspaces.
126-
async fn claude_session_context_for_event(event_json: &str) -> String {
125+
/// Builds the Claude `SessionStart` context for code workspaces.
126+
///
127+
/// On an initialized project this injects the full `using-tracedecay`
128+
/// adoption contract (the `<EXTREMELY_IMPORTANT>` bootstrap) after the index
129+
/// status line, matching the Cursor and Codex session hooks so Claude's
130+
/// `SessionStart` channel carries the same mandate.
131+
pub async fn claude_session_context_for_event(event_json: &str) -> String {
127132
let parsed = serde_json::from_str::<Value>(event_json).unwrap_or(Value::Null);
128133
match codex_project_root_from_parsed_event(&parsed) {
129134
Some(root) => {
130135
let (staleness, _) = cursor_index_signals_for_root(&root).await;
131-
index_status_line(true, staleness.as_deref())
136+
let mut context = index_status_line(true, staleness.as_deref());
137+
append_tracedecay_bootstrap_context(&mut context);
138+
context
132139
}
133140
None if event_cwd_from_parsed(&parsed)
134141
.as_deref()

src/hooks/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ mod steering;
3333
pub mod tool_hints;
3434

3535
pub use claude::{
36-
evaluate_hook_decision, hook_claude_post_tool_use, hook_claude_session_start,
37-
hook_pre_tool_use, hook_prompt_submit, hook_stop,
36+
claude_session_context_for_event, evaluate_hook_decision, hook_claude_post_tool_use,
37+
hook_claude_session_start, hook_pre_tool_use, hook_prompt_submit, hook_stop,
3838
};
3939
pub use codex::{
4040
codex_additional_context_json, codex_apply_patch_rel_paths, codex_project_root_from_event,

src/hooks/steering.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub const CURSOR_PLUGIN_SKILLS: &[&str] = &[
3939
const TRACEDECAY_BOOTSTRAP_SKILL: &str =
4040
include_str!("../../cursor-plugin/skills/using-tracedecay/SKILL.md");
4141

42-
fn append_tracedecay_bootstrap_context(s: &mut String) {
42+
pub(super) fn append_tracedecay_bootstrap_context(s: &mut String) {
4343
s.push_str(
4444
"<EXTREMELY_IMPORTANT>\n\
4545
This project has TraceDecay code-graph support. Below is the full `tracedecay:using-tracedecay` \

tests/agent_suite/kiro_agent_test.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ fn test_install_creates_global_mcp_steering_agent_and_default() {
131131
"global Kiro tracedecay.md should exist"
132132
);
133133
let steering = std::fs::read_to_string(&steering_path).unwrap();
134-
assert!(steering.contains("## Prefer tracedecay MCP tools"));
134+
assert!(steering.contains("## TraceDecay: mandatory tool routing"));
135135
assert!(steering.contains("delegate"));
136136

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

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

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

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

@@ -368,7 +368,7 @@ fn test_uninstall_preserves_user_steering_after_tracedecay_block() {
368368

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

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

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

tests/agent_suite/prompt_rules_parity_test.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::common::{EnvVarGuard, PROCESS_ENV_LOCK};
1111

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

1617
struct HostCase {
@@ -61,7 +62,7 @@ fn hosts() -> Vec<HostCase> {
6162
HostCase {
6263
id: "kiro",
6364
rules_path: |home| home.join(".kiro/steering/tracedecay.md"),
64-
marker: STANDARD_MARKER,
65+
marker: KIRO_MARKER,
6566
stale_block_tail: "\n\n<!-- tracedecay:kiro:end -->",
6667
},
6768
]

tests/hooks_lsp_suite/hooks_test.rs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use crate::common::{EnvVarGuard, GLOBAL_DB_ENV, GLOBAL_DB_ENV_LOCK};
22
use std::path::Path;
33
use tracedecay::config::USER_DATA_DIR_ENV;
44
use tracedecay::hooks::{
5-
build_cursor_session_context, codex_additional_context_json, codex_apply_patch_rel_paths,
6-
codex_project_root_from_event, codex_subagent_start_log_line,
5+
build_cursor_session_context, claude_session_context_for_event, codex_additional_context_json,
6+
codex_apply_patch_rel_paths, codex_project_root_from_event, codex_subagent_start_log_line,
77
codex_user_prompt_submit_context_for_event, codex_workspace_status_from_event,
88
cursor_branch_switch_target, cursor_project_root_from_event, cursor_session_start_json,
99
cursor_shell_command_targets_project, cursor_shell_sync_plan,
@@ -828,6 +828,60 @@ fn test_build_codex_session_context_carries_full_steering() {
828828
assert!(uninit.contains("tracedecay_message_search"));
829829
}
830830

831+
#[tokio::test]
832+
async fn test_claude_session_context_injects_bootstrap_when_initialized() {
833+
// On an initialized project Claude's SessionStart additionalContext must
834+
// carry the full using-tracedecay adoption contract, not just the index
835+
// status line (matching Cursor and Codex).
836+
let dir = tempfile::tempdir().unwrap();
837+
std::fs::create_dir_all(dir.path().join(".tracedecay")).unwrap();
838+
std::fs::write(dir.path().join(".tracedecay/tracedecay.db"), "").unwrap();
839+
let event = serde_json::json!({
840+
"hook_event_name": "SessionStart",
841+
"cwd": dir.path().to_str().unwrap(),
842+
})
843+
.to_string();
844+
845+
let context = claude_session_context_for_event(&event).await;
846+
assert!(
847+
context.contains("tracedecay index status: "),
848+
"initialized Claude context keeps the index status line: {context}"
849+
);
850+
assert!(
851+
context.contains("<EXTREMELY_IMPORTANT>"),
852+
"initialized Claude context must inject the bootstrap contract: {context}"
853+
);
854+
assert!(context.contains("tracedecay:using-tracedecay"));
855+
assert!(context.contains("Grep is faster for this"));
856+
assert!(
857+
context.contains("SUBAGENT-STOP"),
858+
"bootstrap must carry the scoped-subagent guard: {context}"
859+
);
860+
// The additionalContext channel wraps it as SessionStart context.
861+
let json = codex_additional_context_json("SessionStart", &context);
862+
assert!(json.contains("<EXTREMELY_IMPORTANT>"));
863+
}
864+
865+
#[tokio::test]
866+
async fn test_claude_session_context_omits_bootstrap_for_unindexed_project() {
867+
// A project-like workspace without an index gets the init nudge, not the
868+
// full contract.
869+
let dir = tempfile::tempdir().unwrap();
870+
std::fs::write(dir.path().join("Cargo.toml"), "[package]\n").unwrap();
871+
let event = serde_json::json!({
872+
"hook_event_name": "SessionStart",
873+
"cwd": dir.path().to_str().unwrap(),
874+
})
875+
.to_string();
876+
877+
let context = claude_session_context_for_event(&event).await;
878+
assert!(context.contains("tracedecay init"));
879+
assert!(
880+
!context.contains("<EXTREMELY_IMPORTANT>"),
881+
"unindexed workspaces should not inject the full contract: {context}"
882+
);
883+
}
884+
831885
#[test]
832886
fn test_build_codex_session_context_for_unindexed_project_suggests_init() {
833887
let context = tracedecay::hooks::build_codex_session_context_for_workspace(

0 commit comments

Comments
 (0)