Skip to content

feat(claude): Add raw hook capture and Claude agent configuration#56

Open
ivke995 wants to merge 6 commits into
mainfrom
feat/claude-diff-trace
Open

feat(claude): Add raw hook capture and Claude agent configuration#56
ivke995 wants to merge 6 commits into
mainfrom
feat/claude-diff-trace

Conversation

@ivke995

@ivke995 ivke995 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@ivke995 ivke995 force-pushed the feat/claude-diff-trace branch 2 times, most recently from 53d3ae6 to a73ccfa Compare June 9, 2026 07:42
ivke995 and others added 6 commits June 9, 2026 15:59
Implement hidden `sce hooks claude-capture` subcommand for raw Claude
hook JSON intake with PostToolUse model enrichment from transcript.
Add Pkl-generated Claude project settings with capture hook config,
and Claude agent/skill/command definitions for the SCE workflow.

- Add ClaudeCapture subcommand to hooks CLI surface (hidden)
- Add ClaudeCaptureEvent enum: SessionStart, UserPromptSubmit,
  PostToolUse, Stop
- Implement PostToolUse model enrichment via claude_transcript
  JSONL transcript model extraction helper
- Refactor trace persistence into reusable building blocks
  (persist_serialized_trace_payload_at,
  persist_trace_payload_with_retries)
- Add RepoPaths::claude_capture_tmp_dir() for canonical path
  ownership of context/tmp/claude/
- Add Pkl-generated config/.claude/settings.json registering
  capture hooks for all four event types
- Add Claude agent, command, and skill definitions under .claude/
  enabling SCE workflow in Claude Code
- Update context documentation for new Claude capture surface

Co-authored-by: SCE <sce@crocoder.dev>
… intake

Remove the `sce hooks claude-capture` hidden CLI route,
`ClaudeCaptureEvent` enum variant, `claude_transcript.rs` enrichment
module, and `RepoPaths::claude_capture_tmp_dir()` — the raw JSON
diagnostic path that wrote Claude hook payloads under
`context/tmp/claude/` without DB persistence or cross-editor reuse.

Add `sce hooks session-model` STDIN intake for normalized model
attribution upsert into the new AgentTraceDb `session_models` table
(migration 008).  `diff-trace` now accepts optional `model_id` and
resolves it from `session_models` when absent, enabling cross-editor
reuse: OpenCode sends `model_id` directly, Claude relies on DB
resolution.

Generate a Claude TypeScript event-adapter runtime at
`.claude/plugins/sce-agent-trace.ts` that emits normalized
`session-model` and `diff-trace` payloads to the shared Rust intake.
Update the generated-config pipeline (Pkl renderers, OpenCode plugin
registration, settings) and build fileset to include the new
agent-trace-plugin source tree.

Update `context/` docs to reflect the removed raw capture route, the
new session-model command routing, and the cross-editor shared boundary
at `sce hooks diff-trace`.

Co-authored-by: SCE <sce@crocoder.dev>
Remove the unused CLI dev-dependency from Cargo.toml and Cargo.lock, leaving the crate without declared dev-dependencies.

Update the documented CLI dependency and styling baseline to reflect the current manifest state.

Co-authored-by: SCE <sce@crocoder.dev>
Claude sends model attribution via `session-model` at SessionStart
rather than including model_id in every diff-trace payload. The parser
previously required model_id, rejecting all Claude diff traces.

- Change DiffTraceInsert.model_id from &str to Option<&str>
- Change DiffTracePayload.model_id from String to Option<String>
- Add optional_string_field helper for JSON parsing
- Wire resolve_model closure for DB lookup when model_id absent
- Thread resolved model_id through persistence functions

Co-authored-by: SCE <sce@crocoder.dev>
Add checked-in diff_creation fixtures for Claude Write/Edit PostToolUse payloads and test deriveClaudeDiffTracePayload through the exported derivation seam. Cover create and edit scenarios with exact expected patch comparisons, and update Agent Trace and patch-service context to document the fixture coverage.

Co-authored-by: SCE <sce@crocoder.dev>
Delete unused input.json sidecars from the Claude diff-creation fixture suite.
The active golden tests consume claude-post-tool-use.json and expected.patch, so
the patch-service context now documents that fixture contract directly.

Co-authored-by: SCE <sce@crocoder.dev>
@ivke995 ivke995 force-pushed the feat/claude-diff-trace branch from d8df734 to 01a6359 Compare June 9, 2026 14:15
@ivke995 ivke995 marked this pull request as ready for review June 9, 2026 14:18
format!("Invalid session-model payload from STDIN: {detail}.")
}

fn sm_non_empty(payload: &serde_json::Map<String, Value>, field: &str) -> Result<String> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have helper to check for non empty string field (required_non_empty_string_field).

clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
fn sm_u64(payload: &serde_json::Map<String, Value>, field: &str) -> Result<u64> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is duplicate of required_u64_millisecond_field

)))
}

fn sm_nullable_or_non_empty(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is duplicate of optional_string_field

return;
}

// For PostToolUse, attempt best-effort diff-trace forwarding

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think comment is misleading.

try {
const parseResult = parseClaudeHookPayloadJson(rawJson);
if (parseResult.status !== "ok") {
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could handle this parsing failure better.

["hooks", "session-model"],
`${JSON.stringify({
sessionID: sessionId,
time: currentTimeMs(context.now),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just Date.now()?

Comment on lines +434 to +436
payload.tool_version,
payload.claude_version,
payload.version,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We expect claude to have different properties for version?

}

try {
const parseResult = parseClaudeHookPayloadJson(rawJson);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could parse hook payload at the top of the function and send parsed result to handlers.

Comment on lines +448 to +458
if (value === undefined) {
return undefined;
}

if (value === null) {
return null;
}

if (typeof value !== "string") {
return null;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function could be simplified

function normalizeOptionalVersion(value: unknown): string | null {
	if (!value || typeof value !== "string") {
		return null;
	}

	const normalized = value.trim();
	return normalized.length === 0 ? null : normalized;
}

Comment on lines +183 to +184
"originalFile",
"original_file",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect this property in different formats?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants