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,92 @@
|
|
|
1
|
+
// src/forge-compile-check.ts
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { tmpdir } from "os";
|
|
6
|
+
var STATE_DIR = process.env.CLAUDE_PROJECT_DIR ? join(process.env.CLAUDE_PROJECT_DIR, ".claude", "cache", "forge") : join(tmpdir(), "claude-forge");
|
|
7
|
+
var STATE_FILE = join(STATE_DIR, "compiler-state.json");
|
|
8
|
+
function readStdin() {
|
|
9
|
+
return readFileSync(0, "utf-8");
|
|
10
|
+
}
|
|
11
|
+
function ensureStateDir() {
|
|
12
|
+
if (!existsSync(STATE_DIR)) {
|
|
13
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function saveState(state) {
|
|
17
|
+
ensureStateDir();
|
|
18
|
+
writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
19
|
+
}
|
|
20
|
+
function findProjectRoot(cwd) {
|
|
21
|
+
let dir = cwd;
|
|
22
|
+
while (dir !== "/") {
|
|
23
|
+
if (existsSync(join(dir, "foundry.toml"))) return dir;
|
|
24
|
+
const parent = join(dir, "..");
|
|
25
|
+
if (parent === dir) break;
|
|
26
|
+
dir = parent;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function runForge(filePath, cwd) {
|
|
31
|
+
const projectRoot = findProjectRoot(cwd);
|
|
32
|
+
if (!projectRoot) {
|
|
33
|
+
return { success: true, output: "No foundry.toml found, skipping forge build" };
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const output = execSync("forge build 2>&1", {
|
|
37
|
+
encoding: "utf-8",
|
|
38
|
+
timeout: 6e4,
|
|
39
|
+
maxBuffer: 1024 * 1024,
|
|
40
|
+
cwd: projectRoot
|
|
41
|
+
});
|
|
42
|
+
return { success: true, output };
|
|
43
|
+
} catch (error) {
|
|
44
|
+
const output = error.stdout || error.stderr || error.message;
|
|
45
|
+
return { success: false, output };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function main() {
|
|
49
|
+
const input = JSON.parse(readStdin());
|
|
50
|
+
if (input.tool_name !== "Write" && input.tool_name !== "Edit") {
|
|
51
|
+
console.log("{}");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const filePath = input.tool_input?.file_path || input.tool_response?.filePath || "";
|
|
55
|
+
if (!filePath.endsWith(".sol")) {
|
|
56
|
+
console.log("{}");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const result = runForge(filePath, input.cwd);
|
|
60
|
+
const state = {
|
|
61
|
+
session_id: input.session_id,
|
|
62
|
+
file_path: filePath,
|
|
63
|
+
has_errors: !result.success,
|
|
64
|
+
errors: result.output,
|
|
65
|
+
timestamp: Date.now()
|
|
66
|
+
};
|
|
67
|
+
saveState(state);
|
|
68
|
+
if (!result.success) {
|
|
69
|
+
const errorLines = result.output.split("\n").filter((l) => l.includes("Error") || l.includes("error")).slice(0, 10);
|
|
70
|
+
console.log(JSON.stringify({
|
|
71
|
+
hookSpecificOutput: {
|
|
72
|
+
hookEventName: "PostToolUse",
|
|
73
|
+
additionalContext: `FORGE BUILD ERRORS:
|
|
74
|
+
|
|
75
|
+
${errorLines.join("\n")}
|
|
76
|
+
|
|
77
|
+
Fix compilation errors before proceeding.`
|
|
78
|
+
}
|
|
79
|
+
}));
|
|
80
|
+
} else {
|
|
81
|
+
console.log(JSON.stringify({
|
|
82
|
+
hookSpecificOutput: {
|
|
83
|
+
hookEventName: "PostToolUse",
|
|
84
|
+
additionalContext: "forge build: compilation successful"
|
|
85
|
+
}
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
main().catch((err) => {
|
|
90
|
+
console.error(err.message);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/gas-snapshot-diff.ts
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
function readStdin() {
|
|
6
|
+
return readFileSync(0, "utf-8");
|
|
7
|
+
}
|
|
8
|
+
function findProjectRoot(cwd) {
|
|
9
|
+
let dir = cwd;
|
|
10
|
+
while (dir !== "/") {
|
|
11
|
+
if (existsSync(join(dir, "foundry.toml"))) return dir;
|
|
12
|
+
const parent = join(dir, "..");
|
|
13
|
+
if (parent === dir) break;
|
|
14
|
+
dir = parent;
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
function checkGasSnapshot(cwd) {
|
|
19
|
+
const projectRoot = findProjectRoot(cwd);
|
|
20
|
+
if (!projectRoot) {
|
|
21
|
+
return { hasRegression: false, diff: "" };
|
|
22
|
+
}
|
|
23
|
+
const snapshotFile = join(projectRoot, ".gas-snapshot");
|
|
24
|
+
if (!existsSync(snapshotFile)) {
|
|
25
|
+
return { hasRegression: false, diff: "" };
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const output = execSync("forge snapshot --check --tolerance 5 2>&1", {
|
|
29
|
+
encoding: "utf-8",
|
|
30
|
+
timeout: 12e4,
|
|
31
|
+
maxBuffer: 1024 * 1024,
|
|
32
|
+
cwd: projectRoot
|
|
33
|
+
});
|
|
34
|
+
return { hasRegression: false, diff: output };
|
|
35
|
+
} catch (error) {
|
|
36
|
+
const output = error.stdout || error.stderr || error.message;
|
|
37
|
+
const regressionLines = output.split("\n").filter((l) => l.includes("regression") || l.includes("FAIL")).slice(0, 10);
|
|
38
|
+
return { hasRegression: true, diff: regressionLines.join("\n") };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function main() {
|
|
42
|
+
const input = JSON.parse(readStdin());
|
|
43
|
+
if (input.tool_name !== "Write" && input.tool_name !== "Edit") {
|
|
44
|
+
console.log("{}");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const filePath = input.tool_input?.file_path || input.tool_response?.filePath || "";
|
|
48
|
+
if (!filePath.endsWith(".sol")) {
|
|
49
|
+
console.log("{}");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const result = checkGasSnapshot(input.cwd);
|
|
53
|
+
if (result.hasRegression) {
|
|
54
|
+
console.log(JSON.stringify({
|
|
55
|
+
hookSpecificOutput: {
|
|
56
|
+
hookEventName: "PostToolUse",
|
|
57
|
+
additionalContext: `GAS REGRESSION DETECTED (>5% increase):
|
|
58
|
+
|
|
59
|
+
${result.diff}
|
|
60
|
+
|
|
61
|
+
Review gas impact. Run \`forge snapshot --diff\` for full comparison.`
|
|
62
|
+
}
|
|
63
|
+
}));
|
|
64
|
+
} else {
|
|
65
|
+
console.log("{}");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
main().catch((err) => {
|
|
69
|
+
console.error(err.message);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
});
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// src/memory-awareness.ts
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { spawnSync } from "child_process";
|
|
4
|
+
|
|
5
|
+
// src/shared/opc-path.ts
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
function getOpcDir() {
|
|
9
|
+
const envOpcDir = process.env.CLAUDE_OPC_DIR;
|
|
10
|
+
if (envOpcDir && existsSync(envOpcDir)) {
|
|
11
|
+
return envOpcDir;
|
|
12
|
+
}
|
|
13
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
14
|
+
const localOpc = join(projectDir, "opc");
|
|
15
|
+
if (existsSync(localOpc)) {
|
|
16
|
+
return localOpc;
|
|
17
|
+
}
|
|
18
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
19
|
+
if (homeDir) {
|
|
20
|
+
const globalClaude = join(homeDir, ".claude");
|
|
21
|
+
const globalScripts = join(globalClaude, "scripts", "core");
|
|
22
|
+
if (existsSync(globalScripts)) {
|
|
23
|
+
return globalClaude;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/memory-awareness.ts
|
|
30
|
+
function readStdin() {
|
|
31
|
+
return readFileSync(0, "utf-8");
|
|
32
|
+
}
|
|
33
|
+
function extractIntent(prompt) {
|
|
34
|
+
const metaPhrases = [
|
|
35
|
+
/^(can you|could you|would you|please|help me|i want to|i need to|let's|lets)\s+/gi,
|
|
36
|
+
/^(show me|tell me|find|search for|look for|recall|remember)\s+/gi,
|
|
37
|
+
/^(how do i|how can i|how to|what is|what are|where is|where are)\s+/gi,
|
|
38
|
+
/\s+(for me|please|thanks|thank you)$/gi,
|
|
39
|
+
/\?$/g
|
|
40
|
+
];
|
|
41
|
+
let intent = prompt.trim();
|
|
42
|
+
for (const pattern of metaPhrases) {
|
|
43
|
+
intent = intent.replace(pattern, "");
|
|
44
|
+
}
|
|
45
|
+
intent = intent.trim();
|
|
46
|
+
if (intent.length < 5) {
|
|
47
|
+
return extractKeywords(prompt);
|
|
48
|
+
}
|
|
49
|
+
return intent;
|
|
50
|
+
}
|
|
51
|
+
function extractKeywords(prompt) {
|
|
52
|
+
const stopWords = /* @__PURE__ */ new Set([
|
|
53
|
+
"a",
|
|
54
|
+
"an",
|
|
55
|
+
"the",
|
|
56
|
+
"is",
|
|
57
|
+
"are",
|
|
58
|
+
"was",
|
|
59
|
+
"were",
|
|
60
|
+
"be",
|
|
61
|
+
"been",
|
|
62
|
+
"being",
|
|
63
|
+
"have",
|
|
64
|
+
"has",
|
|
65
|
+
"had",
|
|
66
|
+
"do",
|
|
67
|
+
"does",
|
|
68
|
+
"did",
|
|
69
|
+
"will",
|
|
70
|
+
"would",
|
|
71
|
+
"could",
|
|
72
|
+
"should",
|
|
73
|
+
"may",
|
|
74
|
+
"might",
|
|
75
|
+
"must",
|
|
76
|
+
"can",
|
|
77
|
+
"to",
|
|
78
|
+
"of",
|
|
79
|
+
"in",
|
|
80
|
+
"for",
|
|
81
|
+
"on",
|
|
82
|
+
"with",
|
|
83
|
+
"at",
|
|
84
|
+
"by",
|
|
85
|
+
"from",
|
|
86
|
+
"as",
|
|
87
|
+
"into",
|
|
88
|
+
"through",
|
|
89
|
+
"during",
|
|
90
|
+
"before",
|
|
91
|
+
"after",
|
|
92
|
+
"above",
|
|
93
|
+
"below",
|
|
94
|
+
"between",
|
|
95
|
+
"under",
|
|
96
|
+
"again",
|
|
97
|
+
"further",
|
|
98
|
+
"then",
|
|
99
|
+
"once",
|
|
100
|
+
"here",
|
|
101
|
+
"there",
|
|
102
|
+
"when",
|
|
103
|
+
"where",
|
|
104
|
+
"why",
|
|
105
|
+
"how",
|
|
106
|
+
"all",
|
|
107
|
+
"each",
|
|
108
|
+
"few",
|
|
109
|
+
"more",
|
|
110
|
+
"most",
|
|
111
|
+
"other",
|
|
112
|
+
"some",
|
|
113
|
+
"such",
|
|
114
|
+
"no",
|
|
115
|
+
"nor",
|
|
116
|
+
"not",
|
|
117
|
+
"only",
|
|
118
|
+
"own",
|
|
119
|
+
"same",
|
|
120
|
+
"so",
|
|
121
|
+
"than",
|
|
122
|
+
"too",
|
|
123
|
+
"very",
|
|
124
|
+
"s",
|
|
125
|
+
"t",
|
|
126
|
+
"just",
|
|
127
|
+
"don",
|
|
128
|
+
"now",
|
|
129
|
+
"i",
|
|
130
|
+
"me",
|
|
131
|
+
"my",
|
|
132
|
+
"you",
|
|
133
|
+
"your",
|
|
134
|
+
"we",
|
|
135
|
+
"help",
|
|
136
|
+
"with",
|
|
137
|
+
"our",
|
|
138
|
+
"they",
|
|
139
|
+
"them",
|
|
140
|
+
"their",
|
|
141
|
+
"it",
|
|
142
|
+
"its",
|
|
143
|
+
"this",
|
|
144
|
+
"that",
|
|
145
|
+
"these",
|
|
146
|
+
"what",
|
|
147
|
+
"which",
|
|
148
|
+
"who",
|
|
149
|
+
"whom",
|
|
150
|
+
"and",
|
|
151
|
+
"but",
|
|
152
|
+
"if",
|
|
153
|
+
"or",
|
|
154
|
+
"because",
|
|
155
|
+
"until",
|
|
156
|
+
"while",
|
|
157
|
+
"about",
|
|
158
|
+
"against",
|
|
159
|
+
"also",
|
|
160
|
+
"get",
|
|
161
|
+
"got",
|
|
162
|
+
"make",
|
|
163
|
+
"want",
|
|
164
|
+
"need",
|
|
165
|
+
"look",
|
|
166
|
+
"see",
|
|
167
|
+
"use",
|
|
168
|
+
"like",
|
|
169
|
+
"know",
|
|
170
|
+
"think",
|
|
171
|
+
"take",
|
|
172
|
+
"come",
|
|
173
|
+
"go",
|
|
174
|
+
"say",
|
|
175
|
+
"said",
|
|
176
|
+
"tell",
|
|
177
|
+
"please",
|
|
178
|
+
"help",
|
|
179
|
+
"let",
|
|
180
|
+
"sure",
|
|
181
|
+
"recall",
|
|
182
|
+
"remember",
|
|
183
|
+
"similar",
|
|
184
|
+
"problems",
|
|
185
|
+
"issues"
|
|
186
|
+
]);
|
|
187
|
+
const words = prompt.toLowerCase().replace(/[^\w\s-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w));
|
|
188
|
+
return [...new Set(words)].slice(0, 5).join(" ");
|
|
189
|
+
}
|
|
190
|
+
function checkMemoryRelevance(intent, projectDir) {
|
|
191
|
+
if (!intent || intent.length < 3) return null;
|
|
192
|
+
const opcDir = getOpcDir();
|
|
193
|
+
if (!opcDir) return null;
|
|
194
|
+
const searchTerm = intent.replace(/[_\/]/g, " ").replace(/\b\w{1,2}\b/g, "").replace(/\s+/g, " ").trim();
|
|
195
|
+
const result = spawnSync("uv", [
|
|
196
|
+
"run",
|
|
197
|
+
"python",
|
|
198
|
+
"scripts/core/recall_learnings.py",
|
|
199
|
+
"--query",
|
|
200
|
+
searchTerm,
|
|
201
|
+
// Single keyword for text match
|
|
202
|
+
"--k",
|
|
203
|
+
"3",
|
|
204
|
+
"--json",
|
|
205
|
+
"--text-only"
|
|
206
|
+
// Fast text search for hints
|
|
207
|
+
], {
|
|
208
|
+
encoding: "utf-8",
|
|
209
|
+
cwd: opcDir,
|
|
210
|
+
env: {
|
|
211
|
+
...process.env,
|
|
212
|
+
PYTHONPATH: opcDir
|
|
213
|
+
},
|
|
214
|
+
timeout: 5e3
|
|
215
|
+
// 5s timeout for fast check
|
|
216
|
+
});
|
|
217
|
+
if (result.status !== 0 || !result.stdout) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
const data = JSON.parse(result.stdout);
|
|
222
|
+
if (!data.results || data.results.length === 0) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
const results = data.results.slice(0, 3).map((r) => {
|
|
226
|
+
const content = r.content || "";
|
|
227
|
+
const preview = content.split("\n").filter((l) => l.trim().length > 0).map((l) => l.trim()).join(" ").slice(0, 120);
|
|
228
|
+
return {
|
|
229
|
+
id: (r.id || "unknown").slice(0, 8),
|
|
230
|
+
type: r.learning_type || r.type || "UNKNOWN",
|
|
231
|
+
content: preview + (content.length > 120 ? "..." : ""),
|
|
232
|
+
score: r.score || 0
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
count: data.results.length,
|
|
237
|
+
results
|
|
238
|
+
};
|
|
239
|
+
} catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function main() {
|
|
244
|
+
const input = JSON.parse(readStdin());
|
|
245
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || input.cwd;
|
|
246
|
+
if (process.env.CLAUDE_AGENT_ID) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (input.prompt.length < 15) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (input.prompt.trim().startsWith("/")) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const intent = extractIntent(input.prompt);
|
|
256
|
+
if (intent.length < 3) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const match = checkMemoryRelevance(intent, projectDir);
|
|
260
|
+
if (match) {
|
|
261
|
+
const resultLines = match.results.map(
|
|
262
|
+
(r, i) => `${i + 1}. [${r.type}] ${r.content} (id: ${r.id})`
|
|
263
|
+
).join("\n");
|
|
264
|
+
const claudeContext = `MEMORY MATCH (${match.count} results) for "${intent}":
|
|
265
|
+
${resultLines}
|
|
266
|
+
Use /recall "${intent}" for full content. Disclose if helpful.`;
|
|
267
|
+
console.log(JSON.stringify({
|
|
268
|
+
hookSpecificOutput: {
|
|
269
|
+
hookEventName: "UserPromptSubmit",
|
|
270
|
+
additionalContext: claudeContext
|
|
271
|
+
}
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
main().catch(() => {
|
|
276
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// src/natspec-enforcer.ts
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
function readStdin() {
|
|
4
|
+
return readFileSync(0, "utf-8");
|
|
5
|
+
}
|
|
6
|
+
function checkNatSpec(filePath) {
|
|
7
|
+
if (!existsSync(filePath)) return [];
|
|
8
|
+
const content = readFileSync(filePath, "utf-8");
|
|
9
|
+
const lines = content.split("\n");
|
|
10
|
+
const missing = [];
|
|
11
|
+
for (let i = 0; i < lines.length; i++) {
|
|
12
|
+
const line = lines[i].trim();
|
|
13
|
+
const isFunctionDecl = /^\s*(function\s+\w+)/.test(line);
|
|
14
|
+
if (!isFunctionDecl) continue;
|
|
15
|
+
const isPublicOrExternal = /\b(public|external)\b/.test(line);
|
|
16
|
+
if (!isPublicOrExternal) continue;
|
|
17
|
+
const isOverride = /\boverride\b/.test(line);
|
|
18
|
+
if (isOverride) continue;
|
|
19
|
+
let hasNatSpec = false;
|
|
20
|
+
for (let j = i - 1; j >= Math.max(0, i - 5); j--) {
|
|
21
|
+
const prevLine = lines[j].trim();
|
|
22
|
+
if (prevLine.startsWith("///") || prevLine.startsWith("/**") || prevLine.startsWith("*")) {
|
|
23
|
+
hasNatSpec = true;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
if (prevLine === "" || prevLine === "}" || prevLine === "{") continue;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
if (!hasNatSpec) {
|
|
30
|
+
const funcMatch = line.match(/function\s+(\w+)/);
|
|
31
|
+
const funcName = funcMatch ? funcMatch[1] : "unknown";
|
|
32
|
+
missing.push(`Line ${i + 1}: ${funcName}() missing NatSpec`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return missing;
|
|
36
|
+
}
|
|
37
|
+
async function main() {
|
|
38
|
+
const input = JSON.parse(readStdin());
|
|
39
|
+
if (input.tool_name !== "Write" && input.tool_name !== "Edit") {
|
|
40
|
+
console.log("{}");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const filePath = input.tool_input?.file_path || input.tool_response?.filePath || "";
|
|
44
|
+
if (!filePath.endsWith(".sol")) {
|
|
45
|
+
console.log("{}");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const missing = checkNatSpec(filePath);
|
|
49
|
+
if (missing.length > 0) {
|
|
50
|
+
console.log(JSON.stringify({
|
|
51
|
+
hookSpecificOutput: {
|
|
52
|
+
hookEventName: "PostToolUse",
|
|
53
|
+
additionalContext: `NATSPEC: ${missing.length} public/external function(s) missing documentation:
|
|
54
|
+
|
|
55
|
+
${missing.join("\n")}
|
|
56
|
+
|
|
57
|
+
Add /// @notice, @param, @return for each.`
|
|
58
|
+
}
|
|
59
|
+
}));
|
|
60
|
+
} else {
|
|
61
|
+
console.log("{}");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
main().catch((err) => {
|
|
65
|
+
console.error(err.message);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
});
|