intent-log 0.1.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.
Files changed (71) hide show
  1. package/README.md +64 -0
  2. package/dist/adapter/__tests__/claude-code-log-reader.test.d.ts +1 -0
  3. package/dist/adapter/__tests__/claude-code-log-reader.test.js +197 -0
  4. package/dist/adapter/__tests__/claude-code-log-reader.test.js.map +1 -0
  5. package/dist/adapter/__tests__/filesystem-repository.test.d.ts +1 -0
  6. package/dist/adapter/__tests__/filesystem-repository.test.js +152 -0
  7. package/dist/adapter/__tests__/filesystem-repository.test.js.map +1 -0
  8. package/dist/adapter/__tests__/privacy-filter.test.d.ts +1 -0
  9. package/dist/adapter/__tests__/privacy-filter.test.js +61 -0
  10. package/dist/adapter/__tests__/privacy-filter.test.js.map +1 -0
  11. package/dist/adapter/claude-code-log-reader.d.ts +11 -0
  12. package/dist/adapter/claude-code-log-reader.js +133 -0
  13. package/dist/adapter/claude-code-log-reader.js.map +1 -0
  14. package/dist/adapter/claude-code-summarizer.d.ts +6 -0
  15. package/dist/adapter/claude-code-summarizer.js +93 -0
  16. package/dist/adapter/claude-code-summarizer.js.map +1 -0
  17. package/dist/adapter/filesystem-repository.d.ts +39 -0
  18. package/dist/adapter/filesystem-repository.js +98 -0
  19. package/dist/adapter/filesystem-repository.js.map +1 -0
  20. package/dist/adapter/privacy-filter.d.ts +8 -0
  21. package/dist/adapter/privacy-filter.js +58 -0
  22. package/dist/adapter/privacy-filter.js.map +1 -0
  23. package/dist/cli/collect.d.ts +1 -0
  24. package/dist/cli/collect.js +19 -0
  25. package/dist/cli/collect.js.map +1 -0
  26. package/dist/cli/init.d.ts +1 -0
  27. package/dist/cli/init.js +39 -0
  28. package/dist/cli/init.js.map +1 -0
  29. package/dist/cli/log.d.ts +1 -0
  30. package/dist/cli/log.js +15 -0
  31. package/dist/cli/log.js.map +1 -0
  32. package/dist/cli/reset.d.ts +1 -0
  33. package/dist/cli/reset.js +7 -0
  34. package/dist/cli/reset.js.map +1 -0
  35. package/dist/cli/rm.d.ts +1 -0
  36. package/dist/cli/rm.js +7 -0
  37. package/dist/cli/rm.js.map +1 -0
  38. package/dist/cli/show.d.ts +1 -0
  39. package/dist/cli/show.js +21 -0
  40. package/dist/cli/show.js.map +1 -0
  41. package/dist/domain/__tests__/session.test.d.ts +1 -0
  42. package/dist/domain/__tests__/session.test.js +80 -0
  43. package/dist/domain/__tests__/session.test.js.map +1 -0
  44. package/dist/domain/__tests__/step.test.d.ts +1 -0
  45. package/dist/domain/__tests__/step.test.js +65 -0
  46. package/dist/domain/__tests__/step.test.js.map +1 -0
  47. package/dist/domain/session.d.ts +51 -0
  48. package/dist/domain/session.js +49 -0
  49. package/dist/domain/session.js.map +1 -0
  50. package/dist/domain/step.d.ts +13 -0
  51. package/dist/domain/step.js +98 -0
  52. package/dist/domain/step.js.map +1 -0
  53. package/dist/index.d.ts +2 -0
  54. package/dist/index.js +106 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/port/agent-log-reader.d.ts +6 -0
  57. package/dist/port/agent-log-reader.js +2 -0
  58. package/dist/port/agent-log-reader.js.map +1 -0
  59. package/dist/port/step-repository.d.ts +7 -0
  60. package/dist/port/step-repository.js +2 -0
  61. package/dist/port/step-repository.js.map +1 -0
  62. package/dist/port/summarizer.d.ts +13 -0
  63. package/dist/port/summarizer.js +2 -0
  64. package/dist/port/summarizer.js.map +1 -0
  65. package/dist/usecase/__tests__/collect.test.d.ts +1 -0
  66. package/dist/usecase/__tests__/collect.test.js +135 -0
  67. package/dist/usecase/__tests__/collect.test.js.map +1 -0
  68. package/dist/usecase/collect.d.ts +16 -0
  69. package/dist/usecase/collect.js +190 -0
  70. package/dist/usecase/collect.js.map +1 -0
  71. package/package.json +41 -0
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # intent
2
+
3
+ [![CI](https://github.com/gunubin/intent/actions/workflows/ci.yml/badge.svg)](https://github.com/gunubin/intent/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/intent-log)](https://www.npmjs.com/package/intent-log)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D22-brightgreen)](https://nodejs.org/)
7
+
8
+ **Version control for intent, not just code.**
9
+
10
+ When you build software with AI agents, your codebase changes fast — but the *why* behind each change gets lost in chat logs. `intent` extracts the human intent from your AI coding sessions and turns it into a readable, reviewable history.
11
+
12
+ ## Why
13
+
14
+ Code diffs show *what* changed. Commit messages try to explain *why*. But when you're vibe-coding with AI, the real story lives in your conversation — the problem you described, the direction you chose, the constraints you set.
15
+
16
+ `intent` captures that story automatically. It reads your Claude Code session logs, extracts the human decisions, and produces a structured timeline of intent. Think of it as `git log` for your thinking.
17
+
18
+ ## What it does
19
+
20
+ - **Collects** intent from Claude Code session logs — including plan executions and slash command runs
21
+ - **Summarizes** each session into a structured step: what you asked, why, and what was achieved
22
+ - **Tracks** the evolution of your project through human decisions, not just code changes
23
+
24
+ ## Quick start
25
+
26
+ ```bash
27
+ npm install -g intent-log
28
+
29
+ cd your-project
30
+ intent init
31
+ intent collect
32
+ intent log
33
+ ```
34
+
35
+ ## Commands
36
+
37
+ | Command | Description |
38
+ |---|---|
39
+ | `intent init` | Initialize `.intent/` in your project |
40
+ | `intent collect` | Extract intent from Claude Code session logs |
41
+ | `intent log` | Show the timeline of intent steps |
42
+ | `intent show <step>` | View details of a specific step |
43
+ | `intent rm <step>` | Remove a step |
44
+ | `intent reset` | Clear all collected steps |
45
+
46
+ ## How it works
47
+
48
+ 1. You work with Claude Code as usual
49
+ 2. `intent collect` reads the session logs from `~/.claude/projects/`
50
+ 3. Each session is analyzed — plan executions are parsed directly, regular sessions are summarized via Claude
51
+ 4. The result is a series of numbered steps stored in `.intent/steps/`
52
+
53
+ ## Example output
54
+
55
+ ```
56
+ $ intent log
57
+ 001 feat: 認証機能の追加 [feature] 2025-01-15
58
+ 002 fix: ログインバグの修正 [bugfix] 2025-01-15
59
+ 003 /commit スキル実行 [skill] 2025-01-16
60
+ ```
61
+
62
+ ## License
63
+
64
+ MIT
@@ -0,0 +1,197 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { parseJsonl } from "../claude-code-log-reader.js";
3
+ function jsonl(...lines) {
4
+ return lines.map((l) => JSON.stringify(l)).join("\n");
5
+ }
6
+ describe("parseJsonl", () => {
7
+ it("人間入力(type:user + content:string)のみを抽出する", () => {
8
+ const content = jsonl({
9
+ type: "user",
10
+ message: { role: "user", content: "最初の質問" },
11
+ }, {
12
+ type: "assistant",
13
+ message: {
14
+ role: "assistant",
15
+ content: [{ type: "text", text: "回答" }],
16
+ },
17
+ });
18
+ const turns = parseJsonl(content);
19
+ expect(turns).toHaveLength(1);
20
+ expect(turns[0].userPrompt).toBe("最初の質問");
21
+ expect(turns[0].assistantText).toEqual(["回答"]);
22
+ });
23
+ it("tool_result配列エントリは人間ターンとしてカウントされない", () => {
24
+ const content = jsonl({
25
+ type: "user",
26
+ message: { role: "user", content: "実行して" },
27
+ }, {
28
+ type: "assistant",
29
+ message: {
30
+ role: "assistant",
31
+ content: [{ type: "tool_use", name: "Bash" }],
32
+ },
33
+ }, {
34
+ type: "user",
35
+ message: {
36
+ role: "user",
37
+ content: [{ type: "tool_result", content: "結果" }],
38
+ },
39
+ }, {
40
+ type: "assistant",
41
+ message: {
42
+ role: "assistant",
43
+ content: [{ type: "text", text: "完了" }],
44
+ },
45
+ });
46
+ const turns = parseJsonl(content);
47
+ expect(turns).toHaveLength(1);
48
+ expect(turns[0].userPrompt).toBe("実行して");
49
+ expect(turns[0].toolUses).toEqual(["Bash"]);
50
+ expect(turns[0].assistantText).toEqual(["完了"]);
51
+ });
52
+ it("isSidechain:true エントリはスキップされる", () => {
53
+ const content = jsonl({
54
+ type: "user",
55
+ message: { role: "user", content: "通常の質問" },
56
+ }, {
57
+ type: "assistant",
58
+ isSidechain: true,
59
+ message: {
60
+ role: "assistant",
61
+ content: [{ type: "text", text: "サイドチェーン応答" }],
62
+ },
63
+ }, {
64
+ type: "assistant",
65
+ message: {
66
+ role: "assistant",
67
+ content: [{ type: "text", text: "メイン応答" }],
68
+ },
69
+ });
70
+ const turns = parseJsonl(content);
71
+ expect(turns).toHaveLength(1);
72
+ expect(turns[0].assistantText).toEqual(["メイン応答"]);
73
+ });
74
+ it("isSidechain:false エントリは通常通り処理される", () => {
75
+ const content = jsonl({
76
+ type: "user",
77
+ isSidechain: false,
78
+ message: { role: "user", content: "質問" },
79
+ }, {
80
+ type: "assistant",
81
+ isSidechain: false,
82
+ message: {
83
+ role: "assistant",
84
+ content: [{ type: "text", text: "応答" }],
85
+ },
86
+ });
87
+ const turns = parseJsonl(content);
88
+ expect(turns).toHaveLength(1);
89
+ expect(turns[0].userPrompt).toBe("質問");
90
+ });
91
+ it("空行や不正JSONをスキップする", () => {
92
+ const content = [
93
+ "",
94
+ " ",
95
+ "not json",
96
+ JSON.stringify({
97
+ type: "user",
98
+ message: { role: "user", content: "有効な入力" },
99
+ }),
100
+ "{invalid json}",
101
+ JSON.stringify({
102
+ type: "assistant",
103
+ message: {
104
+ role: "assistant",
105
+ content: [{ type: "text", text: "有効な応答" }],
106
+ },
107
+ }),
108
+ ].join("\n");
109
+ const turns = parseJsonl(content);
110
+ expect(turns).toHaveLength(1);
111
+ expect(turns[0].userPrompt).toBe("有効な入力");
112
+ expect(turns[0].assistantText).toEqual(["有効な応答"]);
113
+ });
114
+ it("複数ターンを正しく区切る", () => {
115
+ const content = jsonl({
116
+ type: "user",
117
+ message: { role: "user", content: "質問1" },
118
+ }, {
119
+ type: "assistant",
120
+ message: {
121
+ role: "assistant",
122
+ content: [{ type: "text", text: "回答1" }],
123
+ },
124
+ }, {
125
+ type: "user",
126
+ message: { role: "user", content: "質問2" },
127
+ }, {
128
+ type: "assistant",
129
+ message: {
130
+ role: "assistant",
131
+ content: [
132
+ { type: "thinking", thinking: "思考中" },
133
+ { type: "text", text: "回答2" },
134
+ ],
135
+ },
136
+ });
137
+ const turns = parseJsonl(content);
138
+ expect(turns).toHaveLength(2);
139
+ expect(turns[0].userPrompt).toBe("質問1");
140
+ expect(turns[0].assistantText).toEqual(["回答1"]);
141
+ expect(turns[1].userPrompt).toBe("質問2");
142
+ expect(turns[1].assistantThinking).toEqual(["思考中"]);
143
+ expect(turns[1].assistantText).toEqual(["回答2"]);
144
+ });
145
+ it("system-reminderタグがユーザープロンプトから除去される", () => {
146
+ const content = jsonl({
147
+ type: "user",
148
+ message: {
149
+ role: "user",
150
+ content: "認証機能を追加して\n<system-reminder>\nsome injected context\n</system-reminder>",
151
+ },
152
+ }, {
153
+ type: "assistant",
154
+ message: {
155
+ role: "assistant",
156
+ content: [{ type: "text", text: "了解" }],
157
+ },
158
+ });
159
+ const turns = parseJsonl(content);
160
+ expect(turns).toHaveLength(1);
161
+ expect(turns[0].userPrompt).toBe("認証機能を追加して");
162
+ });
163
+ it("複数のsystem-reminderタグがすべて除去される", () => {
164
+ const content = jsonl({
165
+ type: "user",
166
+ message: {
167
+ role: "user",
168
+ content: "<system-reminder>first</system-reminder>質問です<system-reminder>second</system-reminder>",
169
+ },
170
+ }, {
171
+ type: "assistant",
172
+ message: {
173
+ role: "assistant",
174
+ content: [{ type: "text", text: "回答" }],
175
+ },
176
+ });
177
+ const turns = parseJsonl(content);
178
+ expect(turns).toHaveLength(1);
179
+ expect(turns[0].userPrompt).toBe("質問です");
180
+ });
181
+ it("messageなしのエントリはスキップされる", () => {
182
+ const content = jsonl({ type: "progress" }, {
183
+ type: "user",
184
+ message: { role: "user", content: "質問" },
185
+ }, { type: "system" }, {
186
+ type: "assistant",
187
+ message: {
188
+ role: "assistant",
189
+ content: [{ type: "text", text: "回答" }],
190
+ },
191
+ });
192
+ const turns = parseJsonl(content);
193
+ expect(turns).toHaveLength(1);
194
+ expect(turns[0].userPrompt).toBe("質問");
195
+ });
196
+ });
197
+ //# sourceMappingURL=claude-code-log-reader.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-code-log-reader.test.js","sourceRoot":"","sources":["../../../src/adapter/__tests__/claude-code-log-reader.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,152 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import TOML from "@iarna/toml";
5
+ import { FileSystemRepository } from "../filesystem-repository.js";
6
+ const TEST_DIR = ".intent-test-tmp";
7
+ function setupIntentDir() {
8
+ mkdirSync(join(TEST_DIR, "steps"), { recursive: true });
9
+ const intentToml = {
10
+ project: {
11
+ name: "test",
12
+ description: "test project",
13
+ origin: "test",
14
+ author: "test",
15
+ forked_from: "",
16
+ forked_at_step: 0,
17
+ },
18
+ source: {
19
+ tool: "claude-code",
20
+ version: "1.0.0",
21
+ model: "opus",
22
+ },
23
+ collect: {
24
+ sessions: [],
25
+ },
26
+ };
27
+ writeFileSync(join(TEST_DIR, "intent.toml"), TOML.stringify(intentToml));
28
+ }
29
+ function createRepo() {
30
+ // FileSystemRepository.create() は ".intent" をハードコードしているため、
31
+ // テスト用にリフレクションでインスタンスを生成
32
+ const repo = Object.create(FileSystemRepository.prototype);
33
+ repo.intentDir = TEST_DIR;
34
+ return repo;
35
+ }
36
+ describe("FileSystemRepository", () => {
37
+ beforeEach(() => {
38
+ setupIntentDir();
39
+ });
40
+ afterEach(() => {
41
+ rmSync(TEST_DIR, { recursive: true, force: true });
42
+ });
43
+ describe("listSteps", () => {
44
+ it("空ディレクトリでは空配列を返す", async () => {
45
+ const repo = createRepo();
46
+ const steps = await repo.listSteps();
47
+ expect(steps).toEqual([]);
48
+ });
49
+ it("複数ステップを番号順で返す", async () => {
50
+ const repo = createRepo();
51
+ const step1 = makeStep(1, "最初のステップ");
52
+ const step2 = makeStep(2, "次のステップ");
53
+ await repo.saveStep(step1);
54
+ await repo.saveStep(step2);
55
+ const steps = await repo.listSteps();
56
+ expect(steps).toHaveLength(2);
57
+ expect(steps[0].number).toBe(1);
58
+ expect(steps[1].number).toBe(2);
59
+ });
60
+ });
61
+ describe("saveStep → getStep ラウンドトリップ", () => {
62
+ it("保存したステップを正しく読み取れる", async () => {
63
+ const repo = createRepo();
64
+ const step = makeStep(1, "テストステップ");
65
+ await repo.saveStep(step);
66
+ const loaded = await repo.getStep(1);
67
+ expect(loaded.number).toBe(step.number);
68
+ expect(loaded.title).toBe(step.title);
69
+ expect(loaded.session).toBe(step.session);
70
+ expect(loaded.prompt).toBe(step.prompt);
71
+ expect(loaded.reasoning).toBe(step.reasoning);
72
+ expect(loaded.outcome).toBe(step.outcome);
73
+ expect(loaded.friction).toBe(step.friction);
74
+ expect(loaded.tags).toEqual(step.tags);
75
+ });
76
+ });
77
+ describe("nextStepNumber", () => {
78
+ it("空の場合は1を返す", async () => {
79
+ const repo = createRepo();
80
+ const next = await repo.nextStepNumber();
81
+ expect(next).toBe(1);
82
+ });
83
+ it("既存ステップがある場合は最大+1を返す", async () => {
84
+ const repo = createRepo();
85
+ await repo.saveStep(makeStep(1, "ステップ1"));
86
+ await repo.saveStep(makeStep(3, "ステップ3"));
87
+ const next = await repo.nextStepNumber();
88
+ expect(next).toBe(4);
89
+ });
90
+ });
91
+ describe("isSessionCollected / recordSession", () => {
92
+ it("未収集セッションはfalseを返す", async () => {
93
+ const repo = createRepo();
94
+ const result = await repo.isSessionCollected("session-1");
95
+ expect(result).toBe(false);
96
+ });
97
+ it("recordSession後はtrueを返す", async () => {
98
+ const repo = createRepo();
99
+ await repo.recordSession("session-1");
100
+ const result = await repo.isSessionCollected("session-1");
101
+ expect(result).toBe(true);
102
+ });
103
+ it("同じセッションを二重登録しない", async () => {
104
+ const repo = createRepo();
105
+ await repo.recordSession("session-1");
106
+ await repo.recordSession("session-1");
107
+ const tomlContent = await repo.readIntentToml();
108
+ const count = tomlContent.collect.sessions.filter((s) => s === "session-1").length;
109
+ expect(count).toBe(1);
110
+ });
111
+ });
112
+ describe("removeStep", () => {
113
+ it("指定したステップを削除する", async () => {
114
+ const repo = createRepo();
115
+ await repo.saveStep(makeStep(1, "削除対象"));
116
+ await repo.removeStep(1);
117
+ await expect(repo.getStep(1)).rejects.toThrow("見つかりません");
118
+ });
119
+ it("存在しないステップはエラーになる", async () => {
120
+ const repo = createRepo();
121
+ await expect(repo.removeStep(99)).rejects.toThrow("見つかりません");
122
+ });
123
+ });
124
+ describe("reset", () => {
125
+ it("全ステップを削除しセッション履歴をクリアする", async () => {
126
+ const repo = createRepo();
127
+ await repo.saveStep(makeStep(1, "ステップ1"));
128
+ await repo.saveStep(makeStep(2, "ステップ2"));
129
+ await repo.recordSession("session-1");
130
+ await repo.recordSession("session-2");
131
+ await repo.reset();
132
+ const steps = await repo.listSteps();
133
+ expect(steps).toEqual([]);
134
+ const toml = await repo.readIntentToml();
135
+ expect(toml.collect.sessions).toEqual([]);
136
+ });
137
+ });
138
+ });
139
+ function makeStep(number, title) {
140
+ return {
141
+ number,
142
+ title,
143
+ session: "test-session",
144
+ timestamp: new Date("2025-01-15T10:00:00Z"),
145
+ tags: ["test"],
146
+ prompt: "テストプロンプト",
147
+ reasoning: "テスト理由",
148
+ outcome: "テスト結果",
149
+ friction: "",
150
+ };
151
+ }
152
+ //# sourceMappingURL=filesystem-repository.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filesystem-repository.test.js","sourceRoot":"","sources":["../../../src/adapter/__tests__/filesystem-repository.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,61 @@
1
+ import { describe, it, expect, beforeAll } from "vitest";
2
+ import { PrivacyFilter } from "../privacy-filter.js";
3
+ function makeTurn(prompt) {
4
+ return {
5
+ userPrompt: prompt,
6
+ assistantThinking: [],
7
+ assistantText: [],
8
+ toolUses: [],
9
+ skills: [],
10
+ };
11
+ }
12
+ describe("PrivacyFilter", () => {
13
+ // fromConfig はファイルシステム依存のため、フィルタリングロジックのみテスト
14
+ // config.toml が存在しない場合のデフォルト(excludePatterns: [], minPromptLength: 20)
15
+ let filter;
16
+ beforeAll(async () => {
17
+ // cwd に .intent/config.toml がない場合、デフォルト設定で生成される
18
+ filter = await PrivacyFilter.fromConfig();
19
+ });
20
+ it("短文(20文字未満)を除外する", () => {
21
+ const turns = [makeTurn("短い")];
22
+ const filtered = filter.filterTurns(turns);
23
+ expect(filtered).toHaveLength(0);
24
+ });
25
+ it("system-reminder除去済みのプロンプトはフィルタを通過する", () => {
26
+ // パーサーでsystem-reminderが除去された後の状態をテスト
27
+ const turns = [
28
+ makeTurn("ユーザー認証機能を追加してください。JWTを使ってください。"),
29
+ ];
30
+ const filtered = filter.filterTurns(turns);
31
+ expect(filtered).toHaveLength(1);
32
+ });
33
+ it("エージェント指示文パターン 'You are a ' を除外する", () => {
34
+ const turns = [
35
+ makeTurn("You are a professional coding assistant and should..."),
36
+ ];
37
+ const filtered = filter.filterTurns(turns);
38
+ expect(filtered).toHaveLength(0);
39
+ });
40
+ it("正常なプロンプトは通過する", () => {
41
+ const turns = [
42
+ makeTurn("ユーザー認証機能を追加してください。JWTを使ってください。"),
43
+ ];
44
+ const filtered = filter.filterTurns(turns);
45
+ expect(filtered).toHaveLength(1);
46
+ expect(filtered[0].userPrompt).toBe("ユーザー認証機能を追加してください。JWTを使ってください。");
47
+ });
48
+ it("複数ターンのフィルタリング", () => {
49
+ const turns = [
50
+ makeTurn("短い"),
51
+ makeTurn("これは十分に長い正常なプロンプトです。テスト追加をお願いします。"),
52
+ makeTurn("You are a professional coding assistant and should..."),
53
+ makeTurn("ログイン画面のバグを修正してください。エラーが出ます。"),
54
+ ];
55
+ const filtered = filter.filterTurns(turns);
56
+ expect(filtered).toHaveLength(2);
57
+ expect(filtered[0].userPrompt).toContain("正常なプロンプト");
58
+ expect(filtered[1].userPrompt).toContain("ログイン画面");
59
+ });
60
+ });
61
+ //# sourceMappingURL=privacy-filter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"privacy-filter.test.js","sourceRoot":"","sources":["../../../src/adapter/__tests__/privacy-filter.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,SAAS,QAAQ,CAAC,MAAc;IAC9B,OAAO;QACL,UAAU,EAAE,MAAM;QAClB,iBAAiB,EAAE,EAAE;QACrB,aAAa,EAAE,EAAE;QACjB,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,EAAE;KACX,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,6CAA6C;IAC7C,uEAAuE;IAEvE,IAAI,MAAqB,CAAC;IAE1B,SAAS,CAAC,KAAK,IAAI,EAAE;QACnB,gDAAgD;QAChD,MAAM,GAAG,MAAM,aAAa,CAAC,UAAU,EAAE,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iBAAiB,EAAE,GAAG,EAAE;QACzB,MAAM,KAAK,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QAC/B,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,qCAAqC;QACrC,MAAM,KAAK,GAAG;YACZ,QAAQ,CACN,gCAAgC,CACjC;SACF,CAAC;QACF,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,KAAK,GAAG;YACZ,QAAQ,CAAC,uDAAuD,CAAC;SAClE,CAAC;QACF,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,eAAe,EAAE,GAAG,EAAE;QACvB,MAAM,KAAK,GAAG;YACZ,QAAQ,CAAC,gCAAgC,CAAC;SAC3C,CAAC;QACF,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CACjC,gCAAgC,CACjC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,eAAe,EAAE,GAAG,EAAE;QACvB,MAAM,KAAK,GAAG;YACZ,QAAQ,CAAC,IAAI,CAAC;YACd,QAAQ,CAAC,kCAAkC,CAAC;YAC5C,QAAQ,CAAC,uDAAuD,CAAC;YACjE,QAAQ,CAAC,6BAA6B,CAAC;SACxC,CAAC;QACF,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACrD,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { type ConversationTurn } from "../domain/session.js";
2
+ import type { AgentLogReader } from "../port/agent-log-reader.js";
3
+ export declare class ClaudeCodeLogReader implements AgentLogReader {
4
+ private projectDir;
5
+ private constructor();
6
+ static create(): ClaudeCodeLogReader;
7
+ listSessions(): Promise<string[]>;
8
+ getSessionTimestamp(sessionId: string): Promise<Date>;
9
+ readSession(sessionId: string): Promise<ConversationTurn[]>;
10
+ }
11
+ export declare function parseJsonl(content: string): ConversationTurn[];
@@ -0,0 +1,133 @@
1
+ import { readdir, readFile, stat } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { join, sep } from "node:path";
4
+ import { homedir } from "node:os";
5
+ import { SessionEntry } from "../domain/session.js";
6
+ export class ClaudeCodeLogReader {
7
+ projectDir;
8
+ constructor(projectDir) {
9
+ this.projectDir = projectDir;
10
+ }
11
+ static create() {
12
+ const home = homedir();
13
+ const projectsDir = join(home, ".claude", "projects");
14
+ const cwd = process.cwd();
15
+ const encoded = encodePath(cwd);
16
+ const projectDir = join(projectsDir, encoded);
17
+ if (!existsSync(projectDir)) {
18
+ throw new Error(`Claude Codeのプロジェクトディレクトリが見つかりません: ${projectDir}\n` +
19
+ `このディレクトリでClaude Codeを使ったことがあるか確認してください`);
20
+ }
21
+ return new ClaudeCodeLogReader(projectDir);
22
+ }
23
+ async listSessions() {
24
+ const entries = await readdir(this.projectDir);
25
+ const jsonlFiles = entries.filter((e) => e.endsWith(".jsonl"));
26
+ const filesWithTime = await Promise.all(jsonlFiles.map(async (f) => {
27
+ const s = await stat(join(this.projectDir, f));
28
+ return { name: f.replace(/\.jsonl$/, ""), time: s.birthtimeMs };
29
+ }));
30
+ filesWithTime.sort((a, b) => a.time - b.time);
31
+ return filesWithTime.map((f) => f.name);
32
+ }
33
+ async getSessionTimestamp(sessionId) {
34
+ const path = join(this.projectDir, `${sessionId}.jsonl`);
35
+ if (!existsSync(path)) {
36
+ throw new Error(`セッション '${sessionId}' のログが見つかりません`);
37
+ }
38
+ const s = await stat(path);
39
+ return s.birthtime;
40
+ }
41
+ async readSession(sessionId) {
42
+ const path = join(this.projectDir, `${sessionId}.jsonl`);
43
+ if (!existsSync(path)) {
44
+ throw new Error(`セッション '${sessionId}' のログが見つかりません`);
45
+ }
46
+ return parseJsonl(await readFile(path, "utf-8"));
47
+ }
48
+ }
49
+ function encodePath(path) {
50
+ const normalized = path.startsWith(sep) ? path.slice(1) : path;
51
+ return `-${normalized.replaceAll(sep, "-")}`;
52
+ }
53
+ function stripSystemReminders(text) {
54
+ return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
55
+ }
56
+ export function parseJsonl(content) {
57
+ const turns = [];
58
+ let currentUserPrompt = null;
59
+ let currentThinking = [];
60
+ let currentText = [];
61
+ let currentTools = [];
62
+ let currentSkills = [];
63
+ for (const line of content.split("\n")) {
64
+ const trimmed = line.trim();
65
+ if (!trimmed)
66
+ continue;
67
+ let entry;
68
+ try {
69
+ const raw = JSON.parse(trimmed);
70
+ entry = SessionEntry.parse(raw);
71
+ }
72
+ catch {
73
+ continue;
74
+ }
75
+ const message = entry.message;
76
+ if (!message)
77
+ continue;
78
+ if (entry.isSidechain === true)
79
+ continue;
80
+ if (message.role === "user") {
81
+ if (typeof message.content === "string") {
82
+ // 前のターンを保存
83
+ if (currentUserPrompt !== null) {
84
+ turns.push({
85
+ userPrompt: currentUserPrompt,
86
+ assistantThinking: currentThinking,
87
+ assistantText: currentText,
88
+ toolUses: currentTools,
89
+ skills: currentSkills,
90
+ });
91
+ }
92
+ currentUserPrompt = stripSystemReminders(message.content);
93
+ currentThinking = [];
94
+ currentText = [];
95
+ currentTools = [];
96
+ currentSkills = [];
97
+ }
98
+ }
99
+ else if (message.role === "assistant") {
100
+ if (Array.isArray(message.content)) {
101
+ for (const block of message.content) {
102
+ if (block.type === "thinking" && "thinking" in block) {
103
+ currentThinking.push(block.thinking);
104
+ }
105
+ else if (block.type === "text" && "text" in block) {
106
+ currentText.push(block.text);
107
+ }
108
+ else if (block.type === "tool_use" && "name" in block) {
109
+ currentTools.push(block.name);
110
+ if (block.name === "Skill" && "input" in block) {
111
+ const input = block.input;
112
+ if (typeof input?.skill === "string") {
113
+ currentSkills.push(input.skill);
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
120
+ }
121
+ // 最後のターン
122
+ if (currentUserPrompt !== null) {
123
+ turns.push({
124
+ userPrompt: currentUserPrompt,
125
+ assistantThinking: currentThinking,
126
+ assistantText: currentText,
127
+ toolUses: currentTools,
128
+ skills: currentSkills,
129
+ });
130
+ }
131
+ return turns;
132
+ }
133
+ //# sourceMappingURL=claude-code-log-reader.js.map