runtape 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-54VJGDD2.js → chunk-4AEXXI4C.js} +18 -2
- package/dist/chunk-4AEXXI4C.js.map +1 -0
- package/dist/index.js +85 -2
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +237 -65
- package/dist/types.js +3 -1
- package/package.json +1 -1
- package/dist/chunk-54VJGDD2.js.map +0 -1
|
@@ -12,6 +12,15 @@ var CliAugment = z.object({
|
|
|
12
12
|
sequence: z.number().int().nonnegative(),
|
|
13
13
|
agent_tool_use_id: z.string().min(1).optional()
|
|
14
14
|
});
|
|
15
|
+
var GitContext = z.object({
|
|
16
|
+
repo: z.string().min(1),
|
|
17
|
+
remote_url: z.string().min(1).optional(),
|
|
18
|
+
remote_host: z.string().min(1).optional(),
|
|
19
|
+
remote_slug: z.string().min(1).optional(),
|
|
20
|
+
branch: z.string().min(1).optional(),
|
|
21
|
+
commit_sha: z.string().length(40).optional(),
|
|
22
|
+
is_dirty: z.boolean().optional()
|
|
23
|
+
});
|
|
15
24
|
var SessionStartEvent = ClaudeHookBase.extend(CliAugment.shape).extend({
|
|
16
25
|
type: z.literal("session_start"),
|
|
17
26
|
source: z.string(),
|
|
@@ -19,7 +28,13 @@ var SessionStartEvent = ClaudeHookBase.extend(CliAugment.shape).extend({
|
|
|
19
28
|
// this in on SessionStart (outermost git repo basename, falling back to
|
|
20
29
|
// nearest package.json name, finally to basename(cwd)). Optional so older
|
|
21
30
|
// CLIs that never emit it remain compatible.
|
|
22
|
-
project_name: z.string().min(1).optional()
|
|
31
|
+
project_name: z.string().min(1).optional(),
|
|
32
|
+
// Snapshot of the git state when Claude Code started: repo, branch, HEAD
|
|
33
|
+
// SHA, dirty bit, remote slug for deep-linking. Captured once per session
|
|
34
|
+
// — branch/SHA at runtime drift is acceptable; we anchor "what code was
|
|
35
|
+
// this run started against?". Optional so non-git sessions still ingest
|
|
36
|
+
// cleanly.
|
|
37
|
+
git_context: GitContext.optional()
|
|
23
38
|
});
|
|
24
39
|
var UserPromptEvent = ClaudeHookBase.extend(CliAugment.shape).extend({
|
|
25
40
|
type: z.literal("user_prompt"),
|
|
@@ -95,6 +110,7 @@ var IngestionResponse = z.object({
|
|
|
95
110
|
});
|
|
96
111
|
|
|
97
112
|
export {
|
|
113
|
+
GitContext,
|
|
98
114
|
SessionStartEvent,
|
|
99
115
|
UserPromptEvent,
|
|
100
116
|
ToolAttemptEvent,
|
|
@@ -107,4 +123,4 @@ export {
|
|
|
107
123
|
IngestionRequest,
|
|
108
124
|
IngestionResponse
|
|
109
125
|
};
|
|
110
|
-
//# sourceMappingURL=chunk-
|
|
126
|
+
//# sourceMappingURL=chunk-4AEXXI4C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import { z } from 'zod';\n\n// Every Claude Code hook event carries these envelope fields.\nconst ClaudeHookBase = z.object({\n session_id: z.string().min(1),\n transcript_path: z.string().min(1),\n cwd: z.string().min(1),\n hook_event_name: z.string().min(1),\n permission_mode: z.string().optional(),\n});\n\n// CLI-augmented envelope adds monotonic ordering + wall-clock timestamp.\n// agent_tool_use_id is set when the CLI is synthesizing events from a\n// subagent transcript — it carries the parent Agent step's tool_use_id so\n// the server can resolve parent_step_id deterministically instead of\n// relying on the temporal open-stack heuristic. Optional so the field is\n// absent for normal top-level events.\nconst CliAugment = z.object({\n wall_ts: z.string().datetime(),\n sequence: z.number().int().nonnegative(),\n agent_tool_use_id: z.string().min(1).optional(),\n});\n\n// Git context attached to SessionStart when the cwd is inside a working tree.\n// All inner fields are optional individually — a session in a detached HEAD\n// repo can carry repo + commit_sha but no branch, and a freshly init'd repo\n// can carry repo + branch but no remote_url. The CLI never throws on\n// detection failure; absence of git_context just means \"no repo detected\n// here\" (e.g. /tmp scratch session).\nexport const GitContext = z.object({\n repo: z.string().min(1),\n remote_url: z.string().min(1).optional(),\n remote_host: z.string().min(1).optional(),\n remote_slug: z.string().min(1).optional(),\n branch: z.string().min(1).optional(),\n commit_sha: z.string().length(40).optional(),\n is_dirty: z.boolean().optional(),\n});\n\nexport const SessionStartEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('session_start'),\n source: z.string(),\n // Best-effort project label derived from the session's cwd. The CLI fills\n // this in on SessionStart (outermost git repo basename, falling back to\n // nearest package.json name, finally to basename(cwd)). Optional so older\n // CLIs that never emit it remain compatible.\n project_name: z.string().min(1).optional(),\n // Snapshot of the git state when Claude Code started: repo, branch, HEAD\n // SHA, dirty bit, remote slug for deep-linking. Captured once per session\n // — branch/SHA at runtime drift is acceptable; we anchor \"what code was\n // this run started against?\". Optional so non-git sessions still ingest\n // cleanly.\n git_context: GitContext.optional(),\n});\n\nexport const UserPromptEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('user_prompt'),\n prompt: z.string(),\n});\n\nexport const ToolAttemptEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('tool_attempt'),\n tool_name: z.string().min(1),\n tool_input: z.unknown(),\n tool_use_id: z.string().min(1),\n});\n\nexport const ToolCallEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('tool_call'),\n tool_name: z.string().min(1),\n tool_input: z.unknown(),\n tool_response: z.unknown(),\n tool_use_id: z.string().min(1),\n duration_ms: z.number().nonnegative(),\n // Set by the CLI when the tool_response signals an error (Bash non-zero\n // exit, Edit/Write rejection, tool_response.is_error, etc.). Optional so\n // older CLI versions stay forward-compatible.\n is_error: z.boolean().optional(),\n error_message: z.string().optional(),\n});\n\n// Emitted by the CLI on Stop / PostToolUse after scanning the Claude Code\n// transcript JSONL. One event per assistant message we haven't seen yet,\n// keyed by message_uuid so re-deliveries dedupe at the server. Carries the\n// model identifier and token usage for that turn — the only place either\n// datum is available in Claude Code's emit chain.\nexport const AssistantTurnEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('assistant_turn'),\n message_uuid: z.string().min(1),\n model: z.string().min(1),\n input_tokens: z.number().int().nonnegative(),\n output_tokens: z.number().int().nonnegative(),\n cache_read_tokens: z.number().int().nonnegative().default(0),\n cache_creation_tokens: z.number().int().nonnegative().default(0),\n text: z.string().optional(),\n});\n\nexport const SubagentEndEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('subagent_end'),\n agent_id: z.string().min(1),\n agent_type: z.string().min(1),\n agent_transcript_path: z.string().min(1),\n last_assistant_message: z.string(),\n stop_hook_active: z.boolean().optional(),\n});\n\n// Stop hook (fires every turn). Despite the literal name 'session_end', this\n// is \"turn end\" semantically. Kept for backward compatibility with CLI <= 0.3.x.\n// New CLIs still emit this; the server interprets it as \"run is idle\".\nexport const SessionEndEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('session_end'),\n last_assistant_message: z.string().optional(),\n stop_hook_active: z.boolean().optional(),\n});\n\n// SessionEnd hook (fires once when Claude Code actually closes the session).\n// Server uses this to promote the run from 'idle' to 'ended'.\nexport const SessionCloseEvent = ClaudeHookBase.extend(CliAugment.shape).extend({\n type: z.literal('session_close'),\n reason: z.string().optional(),\n});\n\nexport const RuntapeEvent = z.discriminatedUnion('type', [\n SessionStartEvent,\n UserPromptEvent,\n ToolAttemptEvent,\n ToolCallEvent,\n AssistantTurnEvent,\n SubagentEndEvent,\n SessionEndEvent,\n SessionCloseEvent,\n]);\n\nexport type RuntapeEvent = z.infer<typeof RuntapeEvent>;\n\n// POST /v1/events body — a batch of up to 500 events. The cap MUST match the\n// CLI flusher's BATCH_MAX (src/lib/flusher.ts). They diverged silently from\n// 0.5.0–0.7.0 (flusher = 500, schema = 100), causing every overflow batch to\n// 400 with \"too_big\" → flusher's poison-drop path nuked them, taking out\n// Stop hooks and any other events that piled up with them. Keep these two\n// numbers in lockstep — if you raise one, raise the other.\nexport const IngestionRequest = z.object({\n events: z.array(RuntapeEvent).min(1).max(500),\n});\n\nexport type IngestionRequest = z.infer<typeof IngestionRequest>;\n\n// Response shape (server returns 200 on accepted, 400 on Zod failure, 401 on bad auth).\nexport const IngestionResponse = z.object({\n accepted: z.number().int().nonnegative(),\n errors: z\n .array(\n z.object({\n index: z.number().int().nonnegative(),\n reason: z.string(),\n }),\n )\n .default([]),\n});\n\nexport type IngestionResponse = z.infer<typeof IngestionResponse>;\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RuntapeEvent
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4AEXXI4C.js";
|
|
4
4
|
|
|
5
5
|
// src/index.ts
|
|
6
6
|
import { readFileSync } from "fs";
|
|
@@ -822,6 +822,83 @@ async function findNearestPackageName(cwd) {
|
|
|
822
822
|
return null;
|
|
823
823
|
}
|
|
824
824
|
|
|
825
|
+
// src/lib/git-context.ts
|
|
826
|
+
import { execFile } from "child_process";
|
|
827
|
+
import { basename as basename2 } from "path";
|
|
828
|
+
var GIT_TIMEOUT_MS = 500;
|
|
829
|
+
async function detectGitContext(cwd) {
|
|
830
|
+
const repoRoot = await git(["rev-parse", "--show-toplevel"], cwd);
|
|
831
|
+
if (!repoRoot) return null;
|
|
832
|
+
const [remoteUrl, branchRaw, sha, statusOut] = await Promise.all([
|
|
833
|
+
git(["config", "--get", "remote.origin.url"], cwd),
|
|
834
|
+
git(["rev-parse", "--abbrev-ref", "HEAD"], cwd),
|
|
835
|
+
git(["rev-parse", "HEAD"], cwd),
|
|
836
|
+
// --porcelain emits one line per changed entry; empty output = clean.
|
|
837
|
+
git(["status", "--porcelain"], cwd)
|
|
838
|
+
]);
|
|
839
|
+
const ctx = { repo: basename2(repoRoot) };
|
|
840
|
+
if (remoteUrl) {
|
|
841
|
+
ctx.remote_url = remoteUrl;
|
|
842
|
+
const parsed = parseRemoteUrl(remoteUrl);
|
|
843
|
+
if (parsed) {
|
|
844
|
+
ctx.remote_host = parsed.host;
|
|
845
|
+
ctx.remote_slug = parsed.slug;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (branchRaw && branchRaw !== "HEAD") {
|
|
849
|
+
ctx.branch = branchRaw;
|
|
850
|
+
}
|
|
851
|
+
if (sha && /^[0-9a-f]{40}$/.test(sha)) {
|
|
852
|
+
ctx.commit_sha = sha;
|
|
853
|
+
}
|
|
854
|
+
if (statusOut !== null) {
|
|
855
|
+
ctx.is_dirty = statusOut.length > 0;
|
|
856
|
+
}
|
|
857
|
+
return ctx;
|
|
858
|
+
}
|
|
859
|
+
function git(args, cwd) {
|
|
860
|
+
return new Promise((resolve3) => {
|
|
861
|
+
const child = execFile(
|
|
862
|
+
"git",
|
|
863
|
+
args,
|
|
864
|
+
{
|
|
865
|
+
cwd,
|
|
866
|
+
timeout: GIT_TIMEOUT_MS,
|
|
867
|
+
// 64KB ceiling — `git status --porcelain` on a huge dirty tree could
|
|
868
|
+
// theoretically grow, but we only care about empty-vs-not so we can
|
|
869
|
+
// cap aggressively. If we hit the cap the buffer truncates and we
|
|
870
|
+
// still get the right is_dirty signal.
|
|
871
|
+
maxBuffer: 64 * 1024,
|
|
872
|
+
windowsHide: true
|
|
873
|
+
},
|
|
874
|
+
(err, stdout) => {
|
|
875
|
+
if (err) return resolve3(null);
|
|
876
|
+
resolve3(stdout.toString().trim());
|
|
877
|
+
}
|
|
878
|
+
);
|
|
879
|
+
child.on("error", () => resolve3(null));
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
var REMOTE_PATTERNS = [
|
|
883
|
+
// git@host:owner/repo(.git)?
|
|
884
|
+
/^git@([^:]+):(.+?)(?:\.git)?$/,
|
|
885
|
+
// ssh://git@host/owner/repo(.git)?
|
|
886
|
+
/^ssh:\/\/(?:[^@]+@)?([^/]+)\/(.+?)(?:\.git)?$/,
|
|
887
|
+
// https?://(user@)?host/owner/repo(.git)?
|
|
888
|
+
/^https?:\/\/(?:[^@/]+@)?([^/]+)\/(.+?)(?:\.git)?$/
|
|
889
|
+
];
|
|
890
|
+
function parseRemoteUrl(url) {
|
|
891
|
+
for (const re of REMOTE_PATTERNS) {
|
|
892
|
+
const m = re.exec(url.trim());
|
|
893
|
+
if (m) {
|
|
894
|
+
const host = m[1].toLowerCase();
|
|
895
|
+
const slug = m[2].replace(/\.git$/, "");
|
|
896
|
+
if (slug.includes("/")) return { host, slug };
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
|
|
825
902
|
// src/commands/push.ts
|
|
826
903
|
function isDisabledByEnv() {
|
|
827
904
|
const v = (process.env.RUNTAPE_DISABLE ?? "").trim().toLowerCase();
|
|
@@ -883,10 +960,16 @@ async function pushCommand(opts) {
|
|
|
883
960
|
return 0;
|
|
884
961
|
}
|
|
885
962
|
if (result.event.type === "session_start" && cwd) {
|
|
886
|
-
const projectName = await
|
|
963
|
+
const [projectName, gitContext] = await Promise.all([
|
|
964
|
+
detectProjectName(cwd),
|
|
965
|
+
detectGitContext(cwd)
|
|
966
|
+
]);
|
|
887
967
|
if (projectName) {
|
|
888
968
|
result.event.project_name = projectName;
|
|
889
969
|
}
|
|
970
|
+
if (gitContext) {
|
|
971
|
+
result.event.git_context = gitContext;
|
|
972
|
+
}
|
|
890
973
|
}
|
|
891
974
|
await appendEvent(sessionId, result.event);
|
|
892
975
|
if (opts.event === "PostToolUse" || opts.event === "Stop" || opts.event === "SubagentStop") {
|