refactor: firstCommand

This commit is contained in:
d-kimsuon
2025-10-18 03:39:12 +09:00
parent e45a841656
commit 45ebfad36a
20 changed files with 190 additions and 187 deletions

View File

@@ -1,38 +1,37 @@
import path from "node:path";
import { Path } from "@effect/platform";
import { Effect } from "effect";
import { describe, expect, it } from "vitest";
import { computeClaudeProjectFilePath } from "./computeClaudeProjectFilePath";
describe("computeClaudeProjectFilePath", () => {
const TEST_GLOBAL_CLAUDE_DIR = "/test/mock/claude";
const TEST_PROJECTS_DIR = path.join(TEST_GLOBAL_CLAUDE_DIR, "projects");
it("プロジェクトパスからClaudeの設定ディレクトリパスを計算する", async () => {
const { computeClaudeProjectFilePath } = await import(
"./computeClaudeProjectFilePath"
);
const projectPath = "/home/me/dev/example";
const expected = `${TEST_PROJECTS_DIR}/-home-me-dev-example`;
const result = computeClaudeProjectFilePath({
projectPath,
claudeProjectsDirPath: TEST_PROJECTS_DIR,
});
const result = await Effect.runPromise(
computeClaudeProjectFilePath({
projectPath,
claudeProjectsDirPath: TEST_PROJECTS_DIR,
}).pipe(Effect.provide(Path.layer)),
);
expect(result).toBe(expected);
});
it("末尾にスラッシュがある場合も正しく処理される", async () => {
const { computeClaudeProjectFilePath } = await import(
"./computeClaudeProjectFilePath"
);
const projectPath = "/home/me/dev/example/";
const expected = `${TEST_PROJECTS_DIR}/-home-me-dev-example`;
const result = computeClaudeProjectFilePath({
projectPath,
claudeProjectsDirPath: TEST_PROJECTS_DIR,
});
const result = await Effect.runPromise(
computeClaudeProjectFilePath({
projectPath,
claudeProjectsDirPath: TEST_PROJECTS_DIR,
}).pipe(Effect.provide(Path.layer)),
);
expect(result).toBe(expected);
});

View File

@@ -1,7 +1,7 @@
import { ConversationSchema } from "../../../../lib/conversation-schema";
import type { ErrorJsonl } from "../../types";
import type { ErrorJsonl, ExtendedConversation } from "../../types";
export const parseJsonl = (content: string) => {
export const parseJsonl = (content: string): ExtendedConversation[] => {
const lines = content
.trim()
.split("\n")

View File

@@ -1,10 +1,10 @@
import { parseCommandXml } from "./parseCommandXml";
import { parseUserMessage } from "./parseUserMessage";
describe("parseCommandXml", () => {
describe("command parsing", () => {
it("parses command-name only", () => {
const input = "<command-name>git status</command-name>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result).toEqual({
kind: "command",
@@ -17,7 +17,7 @@ describe("parseCommandXml", () => {
it("parses command-name with command-args", () => {
const input =
"<command-name>git commit</command-name><command-args>-m 'test'</command-args>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result).toEqual({
kind: "command",
@@ -30,7 +30,7 @@ describe("parseCommandXml", () => {
it("parses command-name with command-message", () => {
const input =
"<command-name>ls</command-name><command-message>Listing files</command-message>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result).toEqual({
kind: "command",
@@ -43,7 +43,7 @@ describe("parseCommandXml", () => {
it("parses all command tags together", () => {
const input =
"<command-name>npm install</command-name><command-args>--save-dev vitest</command-args><command-message>Installing test dependencies</command-message>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result).toEqual({
kind: "command",
@@ -56,7 +56,7 @@ describe("parseCommandXml", () => {
it("parses command tags with whitespace in content", () => {
const input =
"<command-name>\n git status \n</command-name><command-args> --short </command-args>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result).toEqual({
kind: "command",
@@ -69,7 +69,7 @@ describe("parseCommandXml", () => {
it("parses command tags in different order", () => {
const input =
"<command-message>Test message</command-message><command-args>-v</command-args><command-name>test command</command-name>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result).toEqual({
kind: "command",
@@ -83,7 +83,7 @@ describe("parseCommandXml", () => {
describe("local-command parsing", () => {
it("parses local-command-stdout", () => {
const input = "<local-command-stdout>output text</local-command-stdout>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result).toEqual({
kind: "local-command",
@@ -94,7 +94,7 @@ describe("parseCommandXml", () => {
it("parses local-command-stdout with multiline content", () => {
const input =
"<local-command-stdout>line1\nline2\nline3</local-command-stdout>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result).toEqual({
kind: "local-command",
@@ -105,7 +105,7 @@ describe("parseCommandXml", () => {
it("parses local-command-stdout with whitespace", () => {
const input =
"<local-command-stdout> \n output with spaces \n </local-command-stdout>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
// The regex pattern preserves all whitespace in content
expect(result).toEqual({
@@ -119,7 +119,7 @@ describe("parseCommandXml", () => {
it("returns command when both command and local-command tags exist", () => {
const input =
"<command-name>test</command-name><local-command-stdout>output</local-command-stdout>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result.kind).toBe("command");
if (result.kind === "command") {
@@ -131,7 +131,7 @@ describe("parseCommandXml", () => {
describe("fallback to text", () => {
it("returns text when no matching tags found", () => {
const input = "just plain text";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result).toEqual({
kind: "text",
@@ -141,7 +141,7 @@ describe("parseCommandXml", () => {
it("returns text when tags are not closed properly", () => {
const input = "<command-name>incomplete";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result).toEqual({
kind: "text",
@@ -151,7 +151,7 @@ describe("parseCommandXml", () => {
it("returns text when tags are mismatched", () => {
const input = "<command-name>test</different-tag>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result).toEqual({
kind: "text",
@@ -161,7 +161,7 @@ describe("parseCommandXml", () => {
it("returns text with empty string", () => {
const input = "";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result).toEqual({
kind: "text",
@@ -171,7 +171,7 @@ describe("parseCommandXml", () => {
it("returns text with only unrecognized tags", () => {
const input = "<unknown-tag>content</unknown-tag>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result).toEqual({
kind: "text",
@@ -184,7 +184,7 @@ describe("parseCommandXml", () => {
it("handles multiple same tags (uses first match)", () => {
const input =
"<command-name>first</command-name><command-name>second</command-name>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result.kind).toBe("command");
if (result.kind === "command") {
@@ -194,7 +194,7 @@ describe("parseCommandXml", () => {
it("handles empty tag content", () => {
const input = "<command-name></command-name>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result).toEqual({
kind: "command",
@@ -207,7 +207,7 @@ describe("parseCommandXml", () => {
it("handles tags with special characters in content", () => {
const input =
"<command-name>git commit -m 'test &amp; demo'</command-name>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result.kind).toBe("command");
if (result.kind === "command") {
@@ -217,7 +217,7 @@ describe("parseCommandXml", () => {
it("does not match nested tags (regex limitation)", () => {
const input = "<command-name><nested>inner</nested>outer</command-name>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
// The regex won't match properly nested tags due to [^<]* pattern
expect(result.kind).toBe("text");
@@ -226,7 +226,7 @@ describe("parseCommandXml", () => {
it("handles tags with surrounding text", () => {
const input =
"Some text before <command-name>test</command-name> and after";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result).toEqual({
kind: "command",
@@ -239,7 +239,7 @@ describe("parseCommandXml", () => {
it("handles newlines between tags", () => {
const input =
"<command-name>test</command-name>\n\n<command-args>arg</command-args>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result).toEqual({
kind: "command",
@@ -252,7 +252,7 @@ describe("parseCommandXml", () => {
it("handles very long content", () => {
const longContent = "x".repeat(10000);
const input = `<command-name>${longContent}</command-name>`;
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result.kind).toBe("command");
if (result.kind === "command") {
@@ -262,7 +262,7 @@ describe("parseCommandXml", () => {
it("handles tags with attributes (not matched)", () => {
const input = '<command-name attr="value">test</command-name>';
const result = parseCommandXml(input);
const result = parseUserMessage(input);
// Tags with attributes won't match because regex expects <tag> not <tag attr="...">
expect(result.kind).toBe("text");
@@ -270,14 +270,14 @@ describe("parseCommandXml", () => {
it("handles self-closing tags (not matched)", () => {
const input = "<command-name />";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result.kind).toBe("text");
});
it("handles Unicode content", () => {
const input = "<command-name>テスト コマンド 🚀</command-name>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result).toEqual({
kind: "command",
@@ -290,7 +290,7 @@ describe("parseCommandXml", () => {
it("handles mixed content with multiple tag types", () => {
const input =
"Some text <command-name>cmd</command-name> more text <unknown>tag</unknown>";
const result = parseCommandXml(input);
const result = parseUserMessage(input);
expect(result.kind).toBe("command");
if (result.kind === "command") {

View File

@@ -7,7 +7,7 @@ const matchSchema = z.object({
content: z.string(),
});
export const parsedCommandSchema = z.union([
export const parsedUserMessageSchema = z.union([
z.object({
kind: z.literal("command"),
commandName: z.string(),
@@ -24,9 +24,9 @@ export const parsedCommandSchema = z.union([
}),
]);
export type ParsedCommand = z.infer<typeof parsedCommandSchema>;
export type ParsedUserMessage = z.infer<typeof parsedUserMessageSchema>;
export const parseCommandXml = (content: string): ParsedCommand => {
export const parseUserMessage = (content: string): ParsedUserMessage => {
const matches = Array.from(content.matchAll(regExp))
.map((match) => matchSchema.safeParse(match.groups))
.filter((result) => result.success)