qualitative-research-pro 1.0.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/AGENTS.md +108 -0
- package/CLAUDE.md +171 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/agents/analysis-orchestrator.md +162 -0
- package/agents/audit-trail-builder.md +127 -0
- package/agents/category-developer.md +179 -0
- package/agents/citation-manager.md +83 -0
- package/agents/constant-comparator.md +135 -0
- package/agents/data-manager.md +104 -0
- package/agents/discussion-writer.md +128 -0
- package/agents/document-analyst.md +114 -0
- package/agents/ethics-reviewer.md +119 -0
- package/agents/field-note-analyst.md +124 -0
- package/agents/fit-assessor.md +192 -0
- package/agents/grounded-theorist.md +210 -0
- package/agents/literature-integrator.md +169 -0
- package/agents/literature-reviewer.md +112 -0
- package/agents/memo-writer.md +234 -0
- package/agents/methodology-critic.md +166 -0
- package/agents/methods-writer.md +109 -0
- package/agents/open-coder.md +187 -0
- package/agents/pattern-analyst.md +166 -0
- package/agents/peer-reviewer.md +129 -0
- package/agents/planner.md +122 -0
- package/agents/proposal-writer.md +108 -0
- package/agents/reflexivity-auditor.md +128 -0
- package/agents/research-designer.md +164 -0
- package/agents/research-writer.md +100 -0
- package/agents/saturation-assessor.md +159 -0
- package/agents/selective-coder.md +167 -0
- package/agents/theoretical-coder.md +260 -0
- package/agents/theoretical-sampler.md +165 -0
- package/agents/transcript-analyst.md +123 -0
- package/bin/cli.mjs +236 -0
- package/hooks/dist/agent-memory-loader.mjs +94 -0
- package/hooks/dist/agent-memory-saver.mjs +113 -0
- package/hooks/dist/bash-audit-log.mjs +71 -0
- package/hooks/dist/credential-deny.mjs +165 -0
- package/hooks/dist/forge-compile-check.mjs +92 -0
- package/hooks/dist/gas-snapshot-diff.mjs +71 -0
- package/hooks/dist/memory-awareness.mjs +276 -0
- package/hooks/dist/natspec-enforcer.mjs +67 -0
- package/hooks/dist/passive-learner.mjs +220 -0
- package/hooks/dist/pre-compact-continuity.mjs +467 -0
- package/hooks/dist/sast-on-edit.mjs +230 -0
- package/hooks/dist/session-analytics.mjs +84 -0
- package/hooks/dist/session-end-cleanup.mjs +121 -0
- package/hooks/dist/session-outcome.mjs +84 -0
- package/hooks/dist/session-register.mjs +307 -0
- package/hooks/dist/session-start-continuity.mjs +405 -0
- package/hooks/dist/slither-on-save.mjs +87 -0
- package/hooks/dist/storage-layout-check.mjs +89 -0
- package/hooks/dist/transcript-parser.mjs +214 -0
- package/install.sh +194 -0
- package/package.json +46 -0
- package/plugin.json +19 -0
- package/rules/academic-writing-style.md +42 -0
- package/rules/citation-standards.md +47 -0
- package/rules/current-methodological-state.md +40 -0
- package/rules/data-handling.md +44 -0
- package/rules/finding-output-format.md +47 -0
- package/rules/gt-coding-standards.md +40 -0
- package/rules/methodological-rigor.md +56 -0
- package/rules/quality-criteria.md +41 -0
- package/rules/reflexivity-requirements.md +40 -0
- package/rules/research-ethics-standards.md +44 -0
- package/skills/.gitkeep +2 -0
- package/skills/academic-writing/SKILL.md +73 -0
- package/skills/action-research/SKILL.md +96 -0
- package/skills/apa-formatting/SKILL.md +85 -0
- package/skills/case-study-methods/SKILL.md +96 -0
- package/skills/category-development/SKILL.md +80 -0
- package/skills/chicago-formatting/SKILL.md +81 -0
- package/skills/coding-pipeline/SKILL.md +81 -0
- package/skills/conceptual-frameworks/SKILL.md +70 -0
- package/skills/constant-comparison/SKILL.md +188 -0
- package/skills/constructivist-gt/SKILL.md +91 -0
- package/skills/data-management-protocols/SKILL.md +67 -0
- package/skills/document-analysis/SKILL.md +66 -0
- package/skills/ethnographic-methods/SKILL.md +82 -0
- package/skills/focus-group-methods/SKILL.md +66 -0
- package/skills/formal-theory/SKILL.md +159 -0
- package/skills/glaserian-grounded-theory/SKILL.md +212 -0
- package/skills/interview-design/SKILL.md +67 -0
- package/skills/literature-synthesis/SKILL.md +71 -0
- package/skills/member-checking/SKILL.md +66 -0
- package/skills/memo-writing/SKILL.md +158 -0
- package/skills/mixed-methods-design/SKILL.md +69 -0
- package/skills/narrative-inquiry/SKILL.md +101 -0
- package/skills/observation-methods/SKILL.md +67 -0
- package/skills/open-coding/SKILL.md +176 -0
- package/skills/paradigmatic-positioning/SKILL.md +72 -0
- package/skills/peer-debriefing/SKILL.md +72 -0
- package/skills/phenomenological-methods/SKILL.md +91 -0
- package/skills/qualitative-rigor/SKILL.md +78 -0
- package/skills/reflexive-practice/SKILL.md +64 -0
- package/skills/research-ethics/SKILL.md +64 -0
- package/skills/research-proposal-writing/SKILL.md +81 -0
- package/skills/research-questions/SKILL.md +66 -0
- package/skills/sampling-strategies/SKILL.md +61 -0
- package/skills/selective-coding/SKILL.md +183 -0
- package/skills/situational-analysis/SKILL.md +93 -0
- package/skills/substantive-theory/SKILL.md +169 -0
- package/skills/thematic-analysis/SKILL.md +80 -0
- package/skills/theoretical-coding/SKILL.md +213 -0
- package/skills/theoretical-sampling/SKILL.md +152 -0
- package/skills/theoretical-saturation/SKILL.md +179 -0
- package/skills/theoretical-sensitivity/SKILL.md +175 -0
- package/skills/theory-integration/SKILL.md +85 -0
- package/skills/thick-description/SKILL.md +69 -0
- package/skills/triangulation/SKILL.md +65 -0
- package/skills/visual-modeling/SKILL.md +66 -0
- package/skills/vulnerable-populations/SKILL.md +69 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// src/passive-learner.ts
|
|
2
|
+
import { readFileSync as readFileSync2, appendFileSync, existsSync as existsSync2, mkdirSync } from "fs";
|
|
3
|
+
import { join as join2, extname, basename as basename2 } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
|
|
6
|
+
// src/shared/project-identity.ts
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
import { createHash } from "crypto";
|
|
9
|
+
import { readFileSync, existsSync } from "fs";
|
|
10
|
+
import { join, basename, resolve } from "path";
|
|
11
|
+
var cachedIdentity = null;
|
|
12
|
+
function getProjectIdentity() {
|
|
13
|
+
if (cachedIdentity) return cachedIdentity;
|
|
14
|
+
const projectPath = getGitRoot();
|
|
15
|
+
if (!projectPath) return null;
|
|
16
|
+
const hash = createHash("md5").update(projectPath).digest("hex").slice(0, 12);
|
|
17
|
+
const name = detectProjectName(projectPath);
|
|
18
|
+
cachedIdentity = { hash, name, path: projectPath };
|
|
19
|
+
return cachedIdentity;
|
|
20
|
+
}
|
|
21
|
+
function getGitRoot() {
|
|
22
|
+
if (process.env.CLAUDE_PROJECT_DIR) {
|
|
23
|
+
return resolve(process.env.CLAUDE_PROJECT_DIR);
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const root = execSync("git rev-parse --show-toplevel", {
|
|
27
|
+
encoding: "utf-8",
|
|
28
|
+
timeout: 500,
|
|
29
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
30
|
+
}).trim();
|
|
31
|
+
return root || null;
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function detectProjectName(projectPath) {
|
|
37
|
+
const pkgPath = join(projectPath, "package.json");
|
|
38
|
+
if (existsSync(pkgPath)) {
|
|
39
|
+
try {
|
|
40
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
41
|
+
if (pkg.name && typeof pkg.name === "string") return pkg.name;
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const goModPath = join(projectPath, "go.mod");
|
|
46
|
+
if (existsSync(goModPath)) {
|
|
47
|
+
try {
|
|
48
|
+
const content = readFileSync(goModPath, "utf-8");
|
|
49
|
+
const match = /^module\s+(\S+)/m.exec(content);
|
|
50
|
+
if (match) {
|
|
51
|
+
const parts = match[1].trim().split("/");
|
|
52
|
+
return parts[parts.length - 1];
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const pyPath = join(projectPath, "pyproject.toml");
|
|
58
|
+
if (existsSync(pyPath)) {
|
|
59
|
+
try {
|
|
60
|
+
const content = readFileSync(pyPath, "utf-8");
|
|
61
|
+
const section = content.match(/\[project\]\s*\n([\s\S]*?)(?:\n\[|$)/);
|
|
62
|
+
if (section) {
|
|
63
|
+
const nameMatch = /^name\s*=\s*"(.+?)"/m.exec(section[1]);
|
|
64
|
+
if (nameMatch) return nameMatch[1];
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const cargoPath = join(projectPath, "Cargo.toml");
|
|
70
|
+
if (existsSync(cargoPath)) {
|
|
71
|
+
try {
|
|
72
|
+
const content = readFileSync(cargoPath, "utf-8");
|
|
73
|
+
const section = content.match(/\[package\]\s*\n([\s\S]*?)(?:\n\[|$)/);
|
|
74
|
+
if (section) {
|
|
75
|
+
const nameMatch = /^name\s*=\s*"(.+?)"/m.exec(section[1]);
|
|
76
|
+
if (nameMatch) return nameMatch[1];
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return basename(projectPath);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/passive-learner.ts
|
|
85
|
+
var ERROR_FIX_PATTERNS = [
|
|
86
|
+
{ error: /Cannot find module ['"](.+?)['"]/i, label: "missing-import" },
|
|
87
|
+
{ error: /Property ['"](.+?)['"] does not exist/i, label: "missing-property" },
|
|
88
|
+
{ error: /Type ['"](.+?)['"] is not assignable/i, label: "type-mismatch" },
|
|
89
|
+
{ error: /ENOENT.*['"](.+?)['"]/i, label: "missing-file" },
|
|
90
|
+
{ error: /SyntaxError/i, label: "syntax-error" }
|
|
91
|
+
];
|
|
92
|
+
function detectFilePattern(filePath) {
|
|
93
|
+
const ext = extname(filePath);
|
|
94
|
+
const name = basename2(filePath);
|
|
95
|
+
if (/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(name)) {
|
|
96
|
+
return {
|
|
97
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
98
|
+
session: "",
|
|
99
|
+
type: "file_pattern",
|
|
100
|
+
pattern: "test-file-creation",
|
|
101
|
+
detail: `Test file: ${name} (${ext})`,
|
|
102
|
+
confidence: 0.5
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (filePath.includes(".claude/hooks")) {
|
|
106
|
+
return {
|
|
107
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
108
|
+
session: "",
|
|
109
|
+
type: "file_pattern",
|
|
110
|
+
pattern: "hook-development",
|
|
111
|
+
detail: `Hook file: ${name}`,
|
|
112
|
+
confidence: 0.5
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
function detectEditPattern(oldStr, newStr) {
|
|
118
|
+
if (!oldStr && /^import\s/.test(newStr.trim())) {
|
|
119
|
+
return {
|
|
120
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
121
|
+
session: "",
|
|
122
|
+
type: "edit_pattern",
|
|
123
|
+
pattern: "add-import",
|
|
124
|
+
detail: newStr.trim().slice(0, 80),
|
|
125
|
+
confidence: 0.3
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (newStr.includes("try") && newStr.includes("catch") && !oldStr.includes("try")) {
|
|
129
|
+
return {
|
|
130
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
131
|
+
session: "",
|
|
132
|
+
type: "edit_pattern",
|
|
133
|
+
pattern: "add-error-handling",
|
|
134
|
+
detail: "try-catch block added",
|
|
135
|
+
confidence: 0.4
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (/:\s*(string|number|boolean|Record|Array|Promise)/.test(newStr) && !/:\s*(string|number|boolean|Record|Array|Promise)/.test(oldStr)) {
|
|
139
|
+
return {
|
|
140
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
141
|
+
session: "",
|
|
142
|
+
type: "edit_pattern",
|
|
143
|
+
pattern: "add-type-annotation",
|
|
144
|
+
detail: "Type annotation added",
|
|
145
|
+
confidence: 0.3
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
function detectErrorFix(output) {
|
|
151
|
+
if (!output) return null;
|
|
152
|
+
for (const { error, label } of ERROR_FIX_PATTERNS) {
|
|
153
|
+
const match = error.exec(output);
|
|
154
|
+
if (match) {
|
|
155
|
+
return {
|
|
156
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
157
|
+
session: "",
|
|
158
|
+
type: "error_fix",
|
|
159
|
+
pattern: label,
|
|
160
|
+
detail: match[0].slice(0, 100),
|
|
161
|
+
confidence: 0.6
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
function main() {
|
|
168
|
+
let raw = "";
|
|
169
|
+
try {
|
|
170
|
+
raw = readFileSync2(0, "utf-8");
|
|
171
|
+
} catch {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (!raw) {
|
|
175
|
+
console.log("{}");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
let input;
|
|
179
|
+
try {
|
|
180
|
+
input = JSON.parse(raw);
|
|
181
|
+
} catch {
|
|
182
|
+
console.log("{}");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const instincts = [];
|
|
186
|
+
const sessionId = input.session_id?.slice(0, 8) || "unknown";
|
|
187
|
+
if (input.tool_name === "Edit" && input.tool_input?.file_path) {
|
|
188
|
+
const fileInst = detectFilePattern(input.tool_input.file_path);
|
|
189
|
+
if (fileInst) instincts.push({ ...fileInst, session: sessionId });
|
|
190
|
+
if (input.tool_input.old_string && input.tool_input.new_string) {
|
|
191
|
+
const editInst = detectEditPattern(input.tool_input.old_string, input.tool_input.new_string);
|
|
192
|
+
if (editInst) instincts.push({ ...editInst, session: sessionId });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (input.tool_name === "Write" && input.tool_input?.file_path) {
|
|
196
|
+
const fileInst = detectFilePattern(input.tool_input.file_path);
|
|
197
|
+
if (fileInst) instincts.push({ ...fileInst, session: sessionId });
|
|
198
|
+
}
|
|
199
|
+
if (input.tool_name === "Bash" && input.tool_output) {
|
|
200
|
+
const errInst = detectErrorFix(input.tool_output);
|
|
201
|
+
if (errInst) instincts.push({ ...errInst, session: sessionId });
|
|
202
|
+
}
|
|
203
|
+
if (instincts.length > 0) {
|
|
204
|
+
const identity = getProjectIdentity();
|
|
205
|
+
if (identity) {
|
|
206
|
+
for (const inst of instincts) {
|
|
207
|
+
inst.project = identity.hash;
|
|
208
|
+
inst.projectName = identity.name;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const logDir = join2(homedir(), ".claude");
|
|
212
|
+
if (!existsSync2(logDir)) mkdirSync(logDir, { recursive: true });
|
|
213
|
+
const logPath = join2(logDir, "instincts.jsonl");
|
|
214
|
+
for (const inst of instincts) {
|
|
215
|
+
appendFileSync(logPath, JSON.stringify(inst) + "\n");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
console.log("{}");
|
|
219
|
+
}
|
|
220
|
+
main();
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
// src/pre-compact-continuity.ts
|
|
2
|
+
import * as fs2 from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { homedir as homedir2 } from "os";
|
|
5
|
+
|
|
6
|
+
// src/transcript-parser.ts
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
function parseTranscript(transcriptPath) {
|
|
9
|
+
const summary = {
|
|
10
|
+
lastTodos: [],
|
|
11
|
+
recentToolCalls: [],
|
|
12
|
+
lastAssistantMessage: "",
|
|
13
|
+
filesModified: [],
|
|
14
|
+
errorsEncountered: []
|
|
15
|
+
};
|
|
16
|
+
if (!fs.existsSync(transcriptPath)) {
|
|
17
|
+
return summary;
|
|
18
|
+
}
|
|
19
|
+
const content = fs.readFileSync(transcriptPath, "utf-8");
|
|
20
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
21
|
+
const allToolCalls = [];
|
|
22
|
+
const modifiedFiles = /* @__PURE__ */ new Set();
|
|
23
|
+
const errors = [];
|
|
24
|
+
let lastTodoState = [];
|
|
25
|
+
let lastAssistant = "";
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
try {
|
|
28
|
+
const entry = JSON.parse(line);
|
|
29
|
+
if (entry.role === "assistant" && typeof entry.content === "string") {
|
|
30
|
+
lastAssistant = entry.content;
|
|
31
|
+
} else if (entry.type === "assistant" && typeof entry.content === "string") {
|
|
32
|
+
lastAssistant = entry.content;
|
|
33
|
+
}
|
|
34
|
+
if (entry.tool_name || entry.type === "tool_use") {
|
|
35
|
+
const toolName = entry.tool_name || entry.name;
|
|
36
|
+
if (toolName) {
|
|
37
|
+
const toolCall = {
|
|
38
|
+
name: toolName,
|
|
39
|
+
timestamp: entry.timestamp,
|
|
40
|
+
input: entry.tool_input,
|
|
41
|
+
success: true
|
|
42
|
+
// Will be updated by result
|
|
43
|
+
};
|
|
44
|
+
if (toolName === "TodoWrite" || toolName.toLowerCase().includes("todowrite")) {
|
|
45
|
+
const input = entry.tool_input;
|
|
46
|
+
if (input?.todos) {
|
|
47
|
+
lastTodoState = input.todos.map((t, idx) => ({
|
|
48
|
+
id: t.id || `todo-${idx}`,
|
|
49
|
+
content: t.content || "",
|
|
50
|
+
status: t.status || "pending"
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (toolName === "Edit" || toolName === "Write" || toolName.toLowerCase().includes("edit") || toolName.toLowerCase().includes("write")) {
|
|
55
|
+
const input = entry.tool_input;
|
|
56
|
+
const filePath = input?.file_path || input?.path;
|
|
57
|
+
if (filePath && typeof filePath === "string") {
|
|
58
|
+
modifiedFiles.add(filePath);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (toolName === "Bash" || toolName.toLowerCase().includes("bash")) {
|
|
62
|
+
const input = entry.tool_input;
|
|
63
|
+
if (input?.command) {
|
|
64
|
+
toolCall.input = { command: input.command };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
allToolCalls.push(toolCall);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (entry.type === "tool_result" || entry.tool_result !== void 0) {
|
|
71
|
+
const result = entry.tool_result;
|
|
72
|
+
if (result) {
|
|
73
|
+
const exitCode = result.exit_code ?? result.exitCode;
|
|
74
|
+
if (exitCode !== void 0 && exitCode !== 0) {
|
|
75
|
+
if (allToolCalls.length > 0) {
|
|
76
|
+
allToolCalls[allToolCalls.length - 1].success = false;
|
|
77
|
+
}
|
|
78
|
+
const errorMsg = result.stderr || result.error || "Command failed";
|
|
79
|
+
const lastTool = allToolCalls[allToolCalls.length - 1];
|
|
80
|
+
const command = lastTool?.input?.command || "unknown command";
|
|
81
|
+
errors.push(`${command}: ${errorMsg.substring(0, 200)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (entry.error) {
|
|
85
|
+
errors.push(entry.error.substring(0, 200));
|
|
86
|
+
if (allToolCalls.length > 0) {
|
|
87
|
+
allToolCalls[allToolCalls.length - 1].success = false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
summary.lastTodos = lastTodoState;
|
|
96
|
+
summary.recentToolCalls = allToolCalls.slice(-5);
|
|
97
|
+
summary.lastAssistantMessage = lastAssistant.substring(0, 500);
|
|
98
|
+
summary.filesModified = Array.from(modifiedFiles);
|
|
99
|
+
summary.errorsEncountered = errors.slice(-5);
|
|
100
|
+
return summary;
|
|
101
|
+
}
|
|
102
|
+
function generateAutoHandoff(summary, sessionName) {
|
|
103
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
104
|
+
const dateOnly = timestamp.split("T")[0];
|
|
105
|
+
const lines = [];
|
|
106
|
+
const inProgress = summary.lastTodos.filter((t) => t.status === "in_progress");
|
|
107
|
+
const pending = summary.lastTodos.filter((t) => t.status === "pending");
|
|
108
|
+
const completed = summary.lastTodos.filter((t) => t.status === "completed");
|
|
109
|
+
const currentTask = inProgress[0]?.content || pending[0]?.content || "Continue from auto-compact";
|
|
110
|
+
const goalSummary = completed.length > 0 ? `Completed ${completed.length} task(s) before auto-compact` : "Session auto-compacted";
|
|
111
|
+
lines.push("---");
|
|
112
|
+
lines.push(`session: ${sessionName}`);
|
|
113
|
+
lines.push(`date: ${dateOnly}`);
|
|
114
|
+
lines.push("status: partial");
|
|
115
|
+
lines.push("outcome: PARTIAL_PLUS");
|
|
116
|
+
lines.push("---");
|
|
117
|
+
lines.push("");
|
|
118
|
+
lines.push(`goal: ${goalSummary}`);
|
|
119
|
+
lines.push(`now: ${currentTask}`);
|
|
120
|
+
lines.push("test: # No test command captured");
|
|
121
|
+
lines.push("");
|
|
122
|
+
lines.push("done_this_session:");
|
|
123
|
+
if (completed.length > 0) {
|
|
124
|
+
completed.forEach((t) => {
|
|
125
|
+
lines.push(` - task: "${t.content.replace(/"/g, '\\"')}"`);
|
|
126
|
+
lines.push(" files: []");
|
|
127
|
+
});
|
|
128
|
+
} else {
|
|
129
|
+
lines.push(' - task: "Session started"');
|
|
130
|
+
lines.push(" files: []");
|
|
131
|
+
}
|
|
132
|
+
lines.push("");
|
|
133
|
+
lines.push("blockers:");
|
|
134
|
+
if (summary.errorsEncountered.length > 0) {
|
|
135
|
+
summary.errorsEncountered.slice(0, 3).forEach((e) => {
|
|
136
|
+
const safeError = e.replace(/"/g, '\\"').substring(0, 100);
|
|
137
|
+
lines.push(` - "${safeError}"`);
|
|
138
|
+
});
|
|
139
|
+
} else {
|
|
140
|
+
lines.push(" []");
|
|
141
|
+
}
|
|
142
|
+
lines.push("");
|
|
143
|
+
lines.push("questions:");
|
|
144
|
+
if (pending.length > 0) {
|
|
145
|
+
pending.slice(0, 3).forEach((t) => {
|
|
146
|
+
lines.push(` - "Resume: ${t.content.replace(/"/g, '\\"')}"`);
|
|
147
|
+
});
|
|
148
|
+
} else {
|
|
149
|
+
lines.push(" []");
|
|
150
|
+
}
|
|
151
|
+
lines.push("");
|
|
152
|
+
lines.push("decisions:");
|
|
153
|
+
lines.push(' - auto_compact: "Context limit reached, auto-compacted"');
|
|
154
|
+
lines.push("");
|
|
155
|
+
lines.push("findings:");
|
|
156
|
+
lines.push(` - tool_calls: "${summary.recentToolCalls.length} recent tool calls"`);
|
|
157
|
+
lines.push(` - files_modified: "${summary.filesModified.length} files changed"`);
|
|
158
|
+
lines.push("");
|
|
159
|
+
lines.push("worked:");
|
|
160
|
+
const successfulTools = summary.recentToolCalls.filter((t) => t.success);
|
|
161
|
+
if (successfulTools.length > 0) {
|
|
162
|
+
lines.push(` - "${successfulTools.map((t) => t.name).join(", ")} completed successfully"`);
|
|
163
|
+
} else {
|
|
164
|
+
lines.push(" []");
|
|
165
|
+
}
|
|
166
|
+
lines.push("");
|
|
167
|
+
lines.push("failed:");
|
|
168
|
+
const failedTools = summary.recentToolCalls.filter((t) => !t.success);
|
|
169
|
+
if (failedTools.length > 0) {
|
|
170
|
+
lines.push(` - "${failedTools.map((t) => t.name).join(", ")} encountered errors"`);
|
|
171
|
+
} else {
|
|
172
|
+
lines.push(" []");
|
|
173
|
+
}
|
|
174
|
+
lines.push("");
|
|
175
|
+
lines.push("next:");
|
|
176
|
+
if (inProgress.length > 0) {
|
|
177
|
+
lines.push(` - "Continue: ${inProgress[0].content.replace(/"/g, '\\"')}"`);
|
|
178
|
+
}
|
|
179
|
+
if (pending.length > 0) {
|
|
180
|
+
pending.slice(0, 2).forEach((t) => {
|
|
181
|
+
lines.push(` - "${t.content.replace(/"/g, '\\"')}"`);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (inProgress.length === 0 && pending.length === 0) {
|
|
185
|
+
lines.push(' - "Review session state and continue"');
|
|
186
|
+
}
|
|
187
|
+
lines.push("");
|
|
188
|
+
lines.push("files:");
|
|
189
|
+
lines.push(" created: []");
|
|
190
|
+
lines.push(" modified:");
|
|
191
|
+
if (summary.filesModified.length > 0) {
|
|
192
|
+
summary.filesModified.slice(0, 10).forEach((f) => {
|
|
193
|
+
lines.push(` - "${f}"`);
|
|
194
|
+
});
|
|
195
|
+
} else {
|
|
196
|
+
lines.push(" []");
|
|
197
|
+
}
|
|
198
|
+
return lines.join("\n");
|
|
199
|
+
}
|
|
200
|
+
var isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
201
|
+
if (isMainModule) {
|
|
202
|
+
const args = process.argv.slice(2);
|
|
203
|
+
if (args.length === 0) {
|
|
204
|
+
console.log("Usage: npx tsx transcript-parser.ts <transcript-path> [session-name]");
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
const transcriptPath = args[0];
|
|
208
|
+
const sessionName = args[1] || "test-session";
|
|
209
|
+
console.log(`Parsing transcript: ${transcriptPath}`);
|
|
210
|
+
const summary = parseTranscript(transcriptPath);
|
|
211
|
+
console.log("\n--- Summary ---");
|
|
212
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
213
|
+
console.log("\n--- Auto-Handoff ---");
|
|
214
|
+
console.log(generateAutoHandoff(summary, sessionName));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/shared/hook-profiler.ts
|
|
218
|
+
import { mkdirSync, existsSync as existsSync2 } from "fs";
|
|
219
|
+
import { join } from "path";
|
|
220
|
+
import { homedir } from "os";
|
|
221
|
+
|
|
222
|
+
// src/shared/log-rotation.ts
|
|
223
|
+
import { statSync, readFileSync as readFileSync2, writeFileSync, appendFileSync, renameSync, unlinkSync } from "fs";
|
|
224
|
+
function appendWithRotation(filePath, line, maxBytes = 2 * 1024 * 1024, keepLines = 5e3) {
|
|
225
|
+
appendFileSync(filePath, line);
|
|
226
|
+
try {
|
|
227
|
+
const stats = statSync(filePath);
|
|
228
|
+
if (stats.size > maxBytes) {
|
|
229
|
+
const tmpPath = filePath + ".rotating";
|
|
230
|
+
try {
|
|
231
|
+
renameSync(filePath, tmpPath);
|
|
232
|
+
const content = readFileSync2(tmpPath, "utf-8");
|
|
233
|
+
const lines = content.split("\n").filter((l) => l.length > 0);
|
|
234
|
+
writeFileSync(filePath, lines.slice(-keepLines).join("\n") + "\n");
|
|
235
|
+
unlinkSync(tmpPath);
|
|
236
|
+
} catch {
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
} catch {
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/shared/hook-profiler.ts
|
|
244
|
+
var PERF_LOG = join(homedir(), ".claude", "cache", "hook-perf.jsonl");
|
|
245
|
+
var MAX_LOG_SIZE = 1024 * 1024;
|
|
246
|
+
function startTimer() {
|
|
247
|
+
return process.hrtime.bigint();
|
|
248
|
+
}
|
|
249
|
+
function endTimer(start, hookName, eventType, sessionId = "unknown") {
|
|
250
|
+
const elapsed = Number(process.hrtime.bigint() - start) / 1e6;
|
|
251
|
+
const entry = {
|
|
252
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
253
|
+
hook: hookName,
|
|
254
|
+
event: eventType,
|
|
255
|
+
duration_ms: Math.round(elapsed * 100) / 100,
|
|
256
|
+
session: sessionId.slice(0, 8)
|
|
257
|
+
};
|
|
258
|
+
try {
|
|
259
|
+
const cacheDir = join(homedir(), ".claude", "cache");
|
|
260
|
+
if (!existsSync2(cacheDir)) mkdirSync(cacheDir, { recursive: true });
|
|
261
|
+
appendWithRotation(PERF_LOG, JSON.stringify(entry) + "\n", MAX_LOG_SIZE, 3e3);
|
|
262
|
+
} catch {
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/pre-compact-continuity.ts
|
|
267
|
+
async function main() {
|
|
268
|
+
const _perfStart = startTimer();
|
|
269
|
+
const input = JSON.parse(await readStdin());
|
|
270
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
271
|
+
const ledgerDir = path.join(projectDir, "thoughts", "ledgers");
|
|
272
|
+
if (!fs2.existsSync(ledgerDir)) {
|
|
273
|
+
const output = {
|
|
274
|
+
continue: true,
|
|
275
|
+
systemMessage: "[PreCompact] No ledger directory found."
|
|
276
|
+
};
|
|
277
|
+
endTimer(_perfStart, "pre-compact-continuity", "PreCompact");
|
|
278
|
+
console.log(JSON.stringify(output));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const ledgerFiles = fs2.readdirSync(ledgerDir).filter((f) => f.startsWith("CONTINUITY_CLAUDE-") && f.endsWith(".md"));
|
|
282
|
+
if (ledgerFiles.length === 0) {
|
|
283
|
+
const output = {
|
|
284
|
+
continue: true,
|
|
285
|
+
systemMessage: "[PreCompact] No ledger found. Create one? /continuity_ledger"
|
|
286
|
+
};
|
|
287
|
+
endTimer(_perfStart, "pre-compact-continuity", "PreCompact");
|
|
288
|
+
console.log(JSON.stringify(output));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const mostRecent = ledgerFiles.sort((a, b) => {
|
|
292
|
+
const statA = fs2.statSync(path.join(ledgerDir, a));
|
|
293
|
+
const statB = fs2.statSync(path.join(ledgerDir, b));
|
|
294
|
+
return statB.mtime.getTime() - statA.mtime.getTime();
|
|
295
|
+
})[0];
|
|
296
|
+
const ledgerPath = path.join(ledgerDir, mostRecent);
|
|
297
|
+
if (input.trigger === "auto") {
|
|
298
|
+
const sessionName = mostRecent.replace("CONTINUITY_CLAUDE-", "").replace(".md", "");
|
|
299
|
+
let handoffFile = "";
|
|
300
|
+
if (input.transcript_path && fs2.existsSync(input.transcript_path)) {
|
|
301
|
+
const summary = parseTranscript(input.transcript_path);
|
|
302
|
+
const handoffContent = generateAutoHandoff(summary, sessionName);
|
|
303
|
+
const handoffDir = path.join(projectDir, "thoughts", "shared", "handoffs", sessionName);
|
|
304
|
+
fs2.mkdirSync(handoffDir, { recursive: true });
|
|
305
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
306
|
+
handoffFile = `auto-handoff-${timestamp}.yaml`;
|
|
307
|
+
const handoffPath = path.join(handoffDir, handoffFile);
|
|
308
|
+
fs2.writeFileSync(handoffPath, handoffContent);
|
|
309
|
+
const briefSummary = generateAutoSummary(projectDir, input.session_id);
|
|
310
|
+
if (briefSummary) {
|
|
311
|
+
appendToLedger(ledgerPath, briefSummary);
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
const briefSummary = generateAutoSummary(projectDir, input.session_id);
|
|
315
|
+
if (briefSummary) {
|
|
316
|
+
appendToLedger(ledgerPath, briefSummary);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
const message = handoffFile ? `[PreCompact:auto] Created YAML handoff: thoughts/shared/handoffs/${sessionName}/${handoffFile}` : `[PreCompact:auto] Session summary auto-appended to ${mostRecent}`;
|
|
320
|
+
const output = {
|
|
321
|
+
continue: true,
|
|
322
|
+
systemMessage: message
|
|
323
|
+
};
|
|
324
|
+
endTimer(_perfStart, "pre-compact-continuity", "PreCompact");
|
|
325
|
+
console.log(JSON.stringify(output));
|
|
326
|
+
} else {
|
|
327
|
+
const output = {
|
|
328
|
+
continue: true,
|
|
329
|
+
systemMessage: `[PreCompact] Consider updating ledger before compacting: /continuity_ledger
|
|
330
|
+
Ledger: ${mostRecent}`
|
|
331
|
+
};
|
|
332
|
+
endTimer(_perfStart, "pre-compact-continuity", "PreCompact");
|
|
333
|
+
console.log(JSON.stringify(output));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function extractStrategicContext(sessionId) {
|
|
337
|
+
const lines = [];
|
|
338
|
+
const claudeDir = path.join(homedir2(), ".claude");
|
|
339
|
+
const ledgerPath = path.join(claudeDir, "canavar", "error-ledger.jsonl");
|
|
340
|
+
if (fs2.existsSync(ledgerPath)) {
|
|
341
|
+
try {
|
|
342
|
+
const allErrors = fs2.readFileSync(ledgerPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
343
|
+
const sessionErrors = allErrors.map((l) => {
|
|
344
|
+
try {
|
|
345
|
+
return JSON.parse(l);
|
|
346
|
+
} catch {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
}).filter((e) => e && e.session === sessionId?.slice(0, 8));
|
|
350
|
+
if (sessionErrors.length > 0) {
|
|
351
|
+
const patterns = [...new Set(sessionErrors.map((e) => e.error_pattern))];
|
|
352
|
+
lines.push(`- Basarisiz yaklasimlar: ${patterns.slice(0, 5).join(", ")}`);
|
|
353
|
+
}
|
|
354
|
+
} catch {
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const intentPath = path.join(claudeDir, "cache", "current-intent.json");
|
|
358
|
+
if (fs2.existsSync(intentPath)) {
|
|
359
|
+
try {
|
|
360
|
+
const intent = JSON.parse(fs2.readFileSync(intentPath, "utf-8"));
|
|
361
|
+
if (intent.task_type && intent.task_type !== "conversational") {
|
|
362
|
+
lines.push(`- Kullanici hedefi: ${intent.task_type} (domain: ${intent.domain?.join(", ") || "genel"})`);
|
|
363
|
+
}
|
|
364
|
+
} catch {
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const maturePath = path.join(claudeDir, "mature-instincts.json");
|
|
368
|
+
if (fs2.existsSync(maturePath)) {
|
|
369
|
+
try {
|
|
370
|
+
const instincts = JSON.parse(fs2.readFileSync(maturePath, "utf-8"));
|
|
371
|
+
const recent = instincts.filter((i) => i.confidence >= 5).slice(0, 3).map((i) => `${i.pattern}(${i.confidence}x)`);
|
|
372
|
+
if (recent.length > 0) {
|
|
373
|
+
lines.push(`- Gozlemlenen pattern'lar: ${recent.join(", ")}`);
|
|
374
|
+
}
|
|
375
|
+
} catch {
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return lines;
|
|
379
|
+
}
|
|
380
|
+
function generateAutoSummary(projectDir, sessionId) {
|
|
381
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
382
|
+
const lines = [];
|
|
383
|
+
const cacheDir = path.join(projectDir, ".claude", "tsc-cache", sessionId || "default");
|
|
384
|
+
const editedFilesPath = path.join(cacheDir, "edited-files.log");
|
|
385
|
+
let editedFiles = [];
|
|
386
|
+
if (fs2.existsSync(editedFilesPath)) {
|
|
387
|
+
const content = fs2.readFileSync(editedFilesPath, "utf-8");
|
|
388
|
+
editedFiles = [...new Set(
|
|
389
|
+
content.split("\n").filter((line) => line.trim()).map((line) => {
|
|
390
|
+
const parts = line.split(":");
|
|
391
|
+
return parts[1]?.replace(projectDir + "/", "") || "";
|
|
392
|
+
}).filter((f) => f)
|
|
393
|
+
)];
|
|
394
|
+
}
|
|
395
|
+
const gitClaudeDir = path.join(projectDir, ".git", "claude", "branches");
|
|
396
|
+
let buildAttempts = { passed: 0, failed: 0 };
|
|
397
|
+
if (fs2.existsSync(gitClaudeDir)) {
|
|
398
|
+
try {
|
|
399
|
+
const branches = fs2.readdirSync(gitClaudeDir);
|
|
400
|
+
for (const branch of branches) {
|
|
401
|
+
const attemptsFile = path.join(gitClaudeDir, branch, "attempts.jsonl");
|
|
402
|
+
if (fs2.existsSync(attemptsFile)) {
|
|
403
|
+
const content = fs2.readFileSync(attemptsFile, "utf-8");
|
|
404
|
+
content.split("\n").filter((l) => l.trim()).forEach((line) => {
|
|
405
|
+
try {
|
|
406
|
+
const attempt = JSON.parse(line);
|
|
407
|
+
if (attempt.type === "build_pass") buildAttempts.passed++;
|
|
408
|
+
if (attempt.type === "build_fail") buildAttempts.failed++;
|
|
409
|
+
} catch {
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
} catch {
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const strategicLines = extractStrategicContext(sessionId);
|
|
418
|
+
if (editedFiles.length === 0 && buildAttempts.passed === 0 && buildAttempts.failed === 0 && strategicLines.length === 0) {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
lines.push(`
|
|
422
|
+
## Session Auto-Summary (${timestamp})`);
|
|
423
|
+
if (editedFiles.length > 0) {
|
|
424
|
+
lines.push(`- Files changed: ${editedFiles.slice(0, 10).join(", ")}${editedFiles.length > 10 ? ` (+${editedFiles.length - 10} more)` : ""}`);
|
|
425
|
+
}
|
|
426
|
+
if (buildAttempts.passed > 0 || buildAttempts.failed > 0) {
|
|
427
|
+
lines.push(`- Build/test: ${buildAttempts.passed} passed, ${buildAttempts.failed} failed`);
|
|
428
|
+
}
|
|
429
|
+
if (strategicLines.length > 0) {
|
|
430
|
+
lines.push("- Stratejik context:");
|
|
431
|
+
for (const sl of strategicLines) {
|
|
432
|
+
lines.push(` ${sl}`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return lines.join("\n");
|
|
436
|
+
}
|
|
437
|
+
function appendToLedger(ledgerPath, summary) {
|
|
438
|
+
try {
|
|
439
|
+
let content = fs2.readFileSync(ledgerPath, "utf-8");
|
|
440
|
+
const stateMatch = content.match(/## State\n/);
|
|
441
|
+
if (stateMatch) {
|
|
442
|
+
const nowMatch = content.match(/(\n-\s*Now:)/);
|
|
443
|
+
if (nowMatch && nowMatch.index) {
|
|
444
|
+
content = content.slice(0, nowMatch.index) + summary + content.slice(nowMatch.index);
|
|
445
|
+
} else {
|
|
446
|
+
const nextSection = content.indexOf("\n## ", content.indexOf("## State") + 1);
|
|
447
|
+
if (nextSection > 0) {
|
|
448
|
+
content = content.slice(0, nextSection) + summary + "\n" + content.slice(nextSection);
|
|
449
|
+
} else {
|
|
450
|
+
content += summary;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
content += summary;
|
|
455
|
+
}
|
|
456
|
+
fs2.writeFileSync(ledgerPath, content);
|
|
457
|
+
} catch (err) {
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
async function readStdin() {
|
|
461
|
+
return new Promise((resolve) => {
|
|
462
|
+
let data = "";
|
|
463
|
+
process.stdin.on("data", (chunk) => data += chunk);
|
|
464
|
+
process.stdin.on("end", () => resolve(data));
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
main().catch(console.error);
|