openwolf 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/LICENSE +663 -0
- package/README.md +232 -0
- package/dist/bin/openwolf.js +10 -0
- package/dist/bin/openwolf.js.map +1 -0
- package/dist/dashboard/assets/AISuggestions-DzE-DQkR.js +1 -0
- package/dist/dashboard/assets/ActivityTimeline-DGVjujnt.js +1 -0
- package/dist/dashboard/assets/AnatomyBrowser-S-2rmYtw.js +1 -0
- package/dist/dashboard/assets/BugLog-CG2zDHJc.js +1 -0
- package/dist/dashboard/assets/CerebrumViewer-Dlgoy69U.js +1 -0
- package/dist/dashboard/assets/CronStatus-DxUF1iW_.js +1 -0
- package/dist/dashboard/assets/DesignQC-BGXn_aq8.js +1 -0
- package/dist/dashboard/assets/MemoryViewer-CGqkTyvQ.js +1 -0
- package/dist/dashboard/assets/ProjectOverview-DlFhu69i.js +1 -0
- package/dist/dashboard/assets/TokenUsage-DDsQiVIq.js +68 -0
- package/dist/dashboard/assets/index-CzK9GUjV.css +1 -0
- package/dist/dashboard/assets/index-PYeNGjkN.js +52 -0
- package/dist/dashboard/index.html +16 -0
- package/dist/hooks/post-read.js +68 -0
- package/dist/hooks/post-write.js +502 -0
- package/dist/hooks/pre-read.js +79 -0
- package/dist/hooks/pre-write.js +120 -0
- package/dist/hooks/session-start.js +76 -0
- package/dist/hooks/shared.js +613 -0
- package/dist/hooks/stop.js +146 -0
- package/dist/src/buglog/bug-matcher.js +3 -0
- package/dist/src/buglog/bug-matcher.js.map +1 -0
- package/dist/src/buglog/bug-tracker.js +81 -0
- package/dist/src/buglog/bug-tracker.js.map +1 -0
- package/dist/src/cli/bug-cmd.js +28 -0
- package/dist/src/cli/bug-cmd.js.map +1 -0
- package/dist/src/cli/cron-cmd.js +106 -0
- package/dist/src/cli/cron-cmd.js.map +1 -0
- package/dist/src/cli/daemon-cmd.js +177 -0
- package/dist/src/cli/daemon-cmd.js.map +1 -0
- package/dist/src/cli/dashboard.js +84 -0
- package/dist/src/cli/dashboard.js.map +1 -0
- package/dist/src/cli/designqc-cmd.js +31 -0
- package/dist/src/cli/designqc-cmd.js.map +1 -0
- package/dist/src/cli/index.js +149 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/cli/init.js +506 -0
- package/dist/src/cli/init.js.map +1 -0
- package/dist/src/cli/registry.js +93 -0
- package/dist/src/cli/registry.js.map +1 -0
- package/dist/src/cli/scan.js +39 -0
- package/dist/src/cli/scan.js.map +1 -0
- package/dist/src/cli/status.js +85 -0
- package/dist/src/cli/status.js.map +1 -0
- package/dist/src/cli/update.js +414 -0
- package/dist/src/cli/update.js.map +1 -0
- package/dist/src/daemon/cron-engine.js +300 -0
- package/dist/src/daemon/cron-engine.js.map +1 -0
- package/dist/src/daemon/file-watcher.js +53 -0
- package/dist/src/daemon/file-watcher.js.map +1 -0
- package/dist/src/daemon/health.js +23 -0
- package/dist/src/daemon/health.js.map +1 -0
- package/dist/src/daemon/wolf-daemon.js +294 -0
- package/dist/src/daemon/wolf-daemon.js.map +1 -0
- package/dist/src/designqc/designqc-capture.js +235 -0
- package/dist/src/designqc/designqc-capture.js.map +1 -0
- package/dist/src/designqc/designqc-engine.js +141 -0
- package/dist/src/designqc/designqc-engine.js.map +1 -0
- package/dist/src/designqc/designqc-types.js +5 -0
- package/dist/src/designqc/designqc-types.js.map +1 -0
- package/dist/src/hooks/post-read.js +69 -0
- package/dist/src/hooks/post-read.js.map +1 -0
- package/dist/src/hooks/post-write.js +503 -0
- package/dist/src/hooks/post-write.js.map +1 -0
- package/dist/src/hooks/pre-read.js +80 -0
- package/dist/src/hooks/pre-read.js.map +1 -0
- package/dist/src/hooks/pre-write.js +121 -0
- package/dist/src/hooks/pre-write.js.map +1 -0
- package/dist/src/hooks/session-start.js +77 -0
- package/dist/src/hooks/session-start.js.map +1 -0
- package/dist/src/hooks/shared.js +614 -0
- package/dist/src/hooks/shared.js.map +1 -0
- package/dist/src/hooks/stop.js +147 -0
- package/dist/src/hooks/stop.js.map +1 -0
- package/dist/src/scanner/anatomy-scanner.js +260 -0
- package/dist/src/scanner/anatomy-scanner.js.map +1 -0
- package/dist/src/scanner/description-extractor.js +1007 -0
- package/dist/src/scanner/description-extractor.js.map +1 -0
- package/dist/src/scanner/project-root.js +42 -0
- package/dist/src/scanner/project-root.js.map +1 -0
- package/dist/src/tracker/token-estimator.js +20 -0
- package/dist/src/tracker/token-estimator.js.map +1 -0
- package/dist/src/tracker/token-ledger.js +45 -0
- package/dist/src/tracker/token-ledger.js.map +1 -0
- package/dist/src/tracker/waste-detector.js +101 -0
- package/dist/src/tracker/waste-detector.js.map +1 -0
- package/dist/src/utils/fs-safe.js +74 -0
- package/dist/src/utils/fs-safe.js.map +1 -0
- package/dist/src/utils/logger.js +48 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/dist/src/utils/paths.js +23 -0
- package/dist/src/utils/paths.js.map +1 -0
- package/dist/src/utils/platform.js +14 -0
- package/dist/src/utils/platform.js.map +1 -0
- package/package.json +77 -0
- package/src/templates/OPENWOLF.md +135 -0
- package/src/templates/anatomy.md +5 -0
- package/src/templates/buglog.json +4 -0
- package/src/templates/cerebrum.md +22 -0
- package/src/templates/claude-md-snippet.md +5 -0
- package/src/templates/claude-rules-openwolf.md +15 -0
- package/src/templates/config.json +73 -0
- package/src/templates/cron-manifest.json +97 -0
- package/src/templates/cron-state.json +7 -0
- package/src/templates/identity.md +9 -0
- package/src/templates/memory.md +4 -0
- package/src/templates/reframe-frameworks.md +597 -0
- package/src/templates/token-ledger.json +21 -0
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as crypto from "node:crypto";
|
|
4
|
+
export function getWolfDir() {
|
|
5
|
+
// Prefer CLAUDE_PROJECT_DIR so hooks work even if CWD changes during a session
|
|
6
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
7
|
+
return path.join(projectDir, ".wolf");
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Bail out silently if .wolf/ directory doesn't exist in the current project.
|
|
11
|
+
* Call this at the top of every hook to avoid crashes in non-OpenWolf projects.
|
|
12
|
+
*/
|
|
13
|
+
export function ensureWolfDir() {
|
|
14
|
+
const wolfDir = getWolfDir();
|
|
15
|
+
if (!fs.existsSync(wolfDir)) {
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function readJSON(filePath, fallback) {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return fallback;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function writeJSON(filePath, data) {
|
|
28
|
+
const dir = path.dirname(filePath);
|
|
29
|
+
if (!fs.existsSync(dir))
|
|
30
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
31
|
+
const tmp = filePath + "." + crypto.randomBytes(4).toString("hex") + ".tmp";
|
|
32
|
+
try {
|
|
33
|
+
fs.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
34
|
+
fs.renameSync(tmp, filePath);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// On Windows, rename can fail if another process holds a handle.
|
|
38
|
+
// Fall back to direct write and clean up the tmp file.
|
|
39
|
+
try {
|
|
40
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
41
|
+
}
|
|
42
|
+
catch { }
|
|
43
|
+
try {
|
|
44
|
+
fs.unlinkSync(tmp);
|
|
45
|
+
}
|
|
46
|
+
catch { }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function readMarkdown(filePath) {
|
|
50
|
+
try {
|
|
51
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return "";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function appendMarkdown(filePath, line) {
|
|
58
|
+
const dir = path.dirname(filePath);
|
|
59
|
+
if (!fs.existsSync(dir))
|
|
60
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
61
|
+
fs.appendFileSync(filePath, line, "utf-8");
|
|
62
|
+
}
|
|
63
|
+
export function parseAnatomy(content) {
|
|
64
|
+
const sections = new Map();
|
|
65
|
+
let currentSection = "";
|
|
66
|
+
for (const line of content.split("\n")) {
|
|
67
|
+
const sm = line.match(/^## (.+)/);
|
|
68
|
+
if (sm) {
|
|
69
|
+
currentSection = sm[1].trim();
|
|
70
|
+
if (!sections.has(currentSection))
|
|
71
|
+
sections.set(currentSection, []);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (!currentSection)
|
|
75
|
+
continue;
|
|
76
|
+
const em = line.match(/^- `([^`]+)`(?:\s+—\s+(.+?))?\s*\(~(\d+)\s+tok\)$/);
|
|
77
|
+
if (em) {
|
|
78
|
+
sections.get(currentSection).push({
|
|
79
|
+
file: em[1],
|
|
80
|
+
description: em[2] || "",
|
|
81
|
+
tokens: parseInt(em[3], 10),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return sections;
|
|
86
|
+
}
|
|
87
|
+
export function serializeAnatomy(sections, metadata) {
|
|
88
|
+
const lines = [
|
|
89
|
+
"# anatomy.md",
|
|
90
|
+
"",
|
|
91
|
+
`> Auto-maintained by OpenWolf. Last scanned: ${metadata.lastScanned}`,
|
|
92
|
+
`> Files: ${metadata.fileCount} tracked | Anatomy hits: ${metadata.hits} | Misses: ${metadata.misses}`,
|
|
93
|
+
"",
|
|
94
|
+
];
|
|
95
|
+
const keys = [...sections.keys()].sort();
|
|
96
|
+
for (const key of keys) {
|
|
97
|
+
lines.push(`## ${key}`);
|
|
98
|
+
lines.push("");
|
|
99
|
+
const entries = sections.get(key).sort((a, b) => a.file.localeCompare(b.file));
|
|
100
|
+
for (const e of entries) {
|
|
101
|
+
const desc = e.description ? ` — ${e.description}` : "";
|
|
102
|
+
lines.push(`- \`${e.file}\`${desc} (~${e.tokens} tok)`);
|
|
103
|
+
}
|
|
104
|
+
lines.push("");
|
|
105
|
+
}
|
|
106
|
+
return lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
export function extractDescription(filePath) {
|
|
109
|
+
const MAX_DESC = 150;
|
|
110
|
+
const basename = path.basename(filePath);
|
|
111
|
+
const ext = path.extname(basename).toLowerCase();
|
|
112
|
+
const known = {
|
|
113
|
+
"package.json": "Node.js package manifest",
|
|
114
|
+
"tsconfig.json": "TypeScript configuration",
|
|
115
|
+
".gitignore": "Git ignore rules",
|
|
116
|
+
"README.md": "Project documentation",
|
|
117
|
+
"composer.json": "PHP package manifest",
|
|
118
|
+
"requirements.txt": "Python dependencies",
|
|
119
|
+
"schema.sql": "Database schema",
|
|
120
|
+
"Dockerfile": "Docker container definition",
|
|
121
|
+
"docker-compose.yml": "Docker Compose services",
|
|
122
|
+
"Cargo.toml": "Rust package manifest",
|
|
123
|
+
"go.mod": "Go module definition",
|
|
124
|
+
"Gemfile": "Ruby dependencies",
|
|
125
|
+
"pubspec.yaml": "Dart/Flutter package manifest",
|
|
126
|
+
};
|
|
127
|
+
if (known[basename])
|
|
128
|
+
return known[basename];
|
|
129
|
+
let content;
|
|
130
|
+
try {
|
|
131
|
+
const fd = fs.openSync(filePath, "r");
|
|
132
|
+
const buf = Buffer.alloc(12288); // 12KB
|
|
133
|
+
const n = fs.readSync(fd, buf, 0, 12288, 0);
|
|
134
|
+
fs.closeSync(fd);
|
|
135
|
+
content = buf.subarray(0, n).toString("utf-8");
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return "";
|
|
139
|
+
}
|
|
140
|
+
if (!content.trim())
|
|
141
|
+
return "";
|
|
142
|
+
const cap = (s) => s.length <= MAX_DESC ? s : s.slice(0, MAX_DESC - 3) + "...";
|
|
143
|
+
// Markdown heading
|
|
144
|
+
if (ext === ".md" || ext === ".mdx") {
|
|
145
|
+
const m = content.match(/^#{1,2}\s+(.+)$/m);
|
|
146
|
+
if (m)
|
|
147
|
+
return cap(m[1].trim());
|
|
148
|
+
}
|
|
149
|
+
// HTML title
|
|
150
|
+
if (ext === ".html" || ext === ".htm") {
|
|
151
|
+
const m = content.match(/<title[^>]*>([^<]+)<\/title>/i);
|
|
152
|
+
if (m)
|
|
153
|
+
return cap(m[1].trim());
|
|
154
|
+
}
|
|
155
|
+
// JSDoc / PHPDoc / Javadoc — first meaningful line
|
|
156
|
+
const jm = content.match(/\/\*\*\s*\n?\s*\*?\s*(.+)/);
|
|
157
|
+
if (jm) {
|
|
158
|
+
const l = jm[1].replace(/\*\/$/, "").trim();
|
|
159
|
+
if (l && !l.startsWith("@") && l.length > 5)
|
|
160
|
+
return cap(l);
|
|
161
|
+
}
|
|
162
|
+
// Python docstring
|
|
163
|
+
if (ext === ".py") {
|
|
164
|
+
const dm = content.match(/^(?:#[^\n]*\n)*\s*(?:"""(.+?)"""|'''(.+?)''')/s);
|
|
165
|
+
if (dm) {
|
|
166
|
+
const first = (dm[1] || dm[2]).split("\n")[0].trim();
|
|
167
|
+
if (first && first.length > 3)
|
|
168
|
+
return cap(first);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Rust doc comments
|
|
172
|
+
if (ext === ".rs") {
|
|
173
|
+
const lines = content.split("\n");
|
|
174
|
+
for (const line of lines.slice(0, 20)) {
|
|
175
|
+
const m = line.match(/^\s*(?:\/\/\/|\/\/!)\s*(.+)/);
|
|
176
|
+
if (m && m[1].length > 5)
|
|
177
|
+
return cap(m[1].trim());
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Go package comment
|
|
181
|
+
if (ext === ".go") {
|
|
182
|
+
const m = content.match(/\/\/\s*Package\s+\w+\s+(.*)/);
|
|
183
|
+
if (m)
|
|
184
|
+
return cap(m[1].trim());
|
|
185
|
+
}
|
|
186
|
+
// C# XML doc
|
|
187
|
+
if (ext === ".cs") {
|
|
188
|
+
const m = content.match(/<summary>\s*([\s\S]*?)\s*<\/summary>/);
|
|
189
|
+
if (m) {
|
|
190
|
+
const text = m[1].replace(/\/\/\/\s*/g, "").replace(/\s+/g, " ").trim();
|
|
191
|
+
if (text.length > 5)
|
|
192
|
+
return cap(text);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Elixir @moduledoc
|
|
196
|
+
if (ext === ".ex" || ext === ".exs") {
|
|
197
|
+
const m = content.match(/@moduledoc\s+"""\s*\n\s*(.*)/);
|
|
198
|
+
if (m)
|
|
199
|
+
return cap(m[1].trim());
|
|
200
|
+
}
|
|
201
|
+
// Header comment (skip generic ones)
|
|
202
|
+
const hdrLines = content.split("\n");
|
|
203
|
+
for (const line of hdrLines.slice(0, 15)) {
|
|
204
|
+
const t = line.trim();
|
|
205
|
+
if (!t || t === "<?php" || t.startsWith("#!") || t.startsWith("namespace") || t.startsWith("use ") || t.startsWith("import ") || t.startsWith("from ") || t.startsWith("require") || t.startsWith("module "))
|
|
206
|
+
continue;
|
|
207
|
+
const cm = t.match(/^(?:\/\/|#|--)\s*(.+)/);
|
|
208
|
+
if (cm) {
|
|
209
|
+
const text = cm[1].trim();
|
|
210
|
+
const lower = text.toLowerCase();
|
|
211
|
+
if (text.length > 5 && !lower.startsWith("copyright") && !lower.startsWith("license") && !lower.startsWith("@") && !lower.startsWith("strict") && !lower.startsWith("generated") && !lower.startsWith("eslint-") && !lower.startsWith("nolint")) {
|
|
212
|
+
return cap(text);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (!t.startsWith("//") && !t.startsWith("#") && !t.startsWith("/*") && !t.startsWith("*") && !t.startsWith("--"))
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
// ─── PHP / Laravel ───────────────────────────────────────
|
|
219
|
+
if (ext === ".php") {
|
|
220
|
+
if (basename.endsWith(".blade.php")) {
|
|
221
|
+
const ext2 = content.match(/@extends\(\s*['"]([^'"]+)['"]\s*\)/);
|
|
222
|
+
const sections = (content.match(/@section\(\s*['"](\w+)['"]/g) || []).map(s => s.match(/['"](\w+)['"]/)?.[1]).filter(Boolean);
|
|
223
|
+
const parts = [];
|
|
224
|
+
if (ext2)
|
|
225
|
+
parts.push(`extends ${ext2[1]}`);
|
|
226
|
+
if (sections.length)
|
|
227
|
+
parts.push(`sections: ${sections.join(", ")}`);
|
|
228
|
+
return cap(parts.length ? `Blade: ${parts.join(", ")}` : "Blade template");
|
|
229
|
+
}
|
|
230
|
+
const classM = content.match(/class\s+(\w+)(?:\s+extends\s+(\w+))?/);
|
|
231
|
+
const className = classM?.[1] || "";
|
|
232
|
+
const parent = classM?.[2] || "";
|
|
233
|
+
const pubMethods = (content.match(/public\s+function\s+(\w+)/g) || [])
|
|
234
|
+
.map(m => m.match(/public\s+function\s+(\w+)/)?.[1])
|
|
235
|
+
.filter(n => n && n !== "__construct" && n !== "middleware");
|
|
236
|
+
if (basename.endsWith("Controller.php") || parent === "Controller") {
|
|
237
|
+
if (pubMethods.length > 0) {
|
|
238
|
+
const display = pubMethods.slice(0, 5).join(", ");
|
|
239
|
+
return cap(pubMethods.length > 5 ? `${display} + ${pubMethods.length - 5} more` : display);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (parent === "Model" || parent === "Authenticatable") {
|
|
243
|
+
const parts = [];
|
|
244
|
+
const tbl = content.match(/\$table\s*=\s*['"]([^'"]+)['"]/);
|
|
245
|
+
if (tbl)
|
|
246
|
+
parts.push(`table: ${tbl[1]}`);
|
|
247
|
+
const fill = content.match(/\$fillable\s*=\s*\[([^\]]*)\]/s);
|
|
248
|
+
if (fill) {
|
|
249
|
+
const c = (fill[1].match(/['"]/g) || []).length / 2;
|
|
250
|
+
parts.push(`${Math.floor(c)} fields`);
|
|
251
|
+
}
|
|
252
|
+
const rels = (content.match(/\$this->(hasMany|hasOne|belongsTo|belongsToMany|morphMany|morphTo)\(/g) || []).length;
|
|
253
|
+
if (rels)
|
|
254
|
+
parts.push(`${rels} rels`);
|
|
255
|
+
return cap(parts.length ? `Model — ${parts.join(", ")}` : `Model: ${className}`);
|
|
256
|
+
}
|
|
257
|
+
if (basename.match(/^\d{4}_\d{2}_\d{2}/)) {
|
|
258
|
+
const create = content.match(/Schema::create\(\s*['"]([^'"]+)['"]/);
|
|
259
|
+
if (create)
|
|
260
|
+
return `Migration: create ${create[1]} table`;
|
|
261
|
+
const alter = content.match(/Schema::table\(\s*['"]([^'"]+)['"]/);
|
|
262
|
+
if (alter)
|
|
263
|
+
return `Migration: alter ${alter[1]} table`;
|
|
264
|
+
return "Database migration";
|
|
265
|
+
}
|
|
266
|
+
if (className && pubMethods.length > 0) {
|
|
267
|
+
const display = pubMethods.slice(0, 4).join(", ");
|
|
268
|
+
return cap(pubMethods.length > 4 ? `${className}: ${display} + ${pubMethods.length - 4} more` : `${className}: ${display}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// ─── TS/JS/React/Next.js ─────────────────────────────────
|
|
272
|
+
if (ext === ".ts" || ext === ".tsx" || ext === ".js" || ext === ".jsx" || ext === ".mjs" || ext === ".cjs") {
|
|
273
|
+
// React component
|
|
274
|
+
if (ext === ".tsx" || ext === ".jsx") {
|
|
275
|
+
const comp = content.match(/(?:export\s+(?:default\s+)?)?(?:function|const)\s+(\w+)/);
|
|
276
|
+
const parts = [];
|
|
277
|
+
if (comp)
|
|
278
|
+
parts.push(comp[1]);
|
|
279
|
+
const renders = [];
|
|
280
|
+
if (/<(?:form|Form)/i.test(content))
|
|
281
|
+
renders.push("form");
|
|
282
|
+
if (/<(?:table|Table|DataTable)/i.test(content))
|
|
283
|
+
renders.push("table");
|
|
284
|
+
if (/<(?:dialog|Dialog|Modal|Drawer)/i.test(content))
|
|
285
|
+
renders.push("modal");
|
|
286
|
+
if (renders.length)
|
|
287
|
+
parts.push(`renders ${renders.join(", ")}`);
|
|
288
|
+
if (parts.length)
|
|
289
|
+
return cap(parts.join(" — "));
|
|
290
|
+
}
|
|
291
|
+
// Next.js conventions
|
|
292
|
+
if (basename === "page.tsx" || basename === "page.js")
|
|
293
|
+
return "Next.js page component";
|
|
294
|
+
if (basename === "layout.tsx" || basename === "layout.js")
|
|
295
|
+
return "Next.js layout";
|
|
296
|
+
if (basename === "route.ts" || basename === "route.js") {
|
|
297
|
+
const methods = [...new Set((content.match(/export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE)/g) || [])
|
|
298
|
+
.map(m => m.match(/(GET|POST|PUT|PATCH|DELETE)/)?.[1]))].filter(Boolean);
|
|
299
|
+
return methods.length ? `Next.js API route: ${methods.join(", ")}` : "Next.js API route";
|
|
300
|
+
}
|
|
301
|
+
// Express/Fastify routes
|
|
302
|
+
const routeHits = content.match(/\.(get|post|put|patch|delete)\s*\(\s*['"`]/g);
|
|
303
|
+
if (routeHits && routeHits.length > 0) {
|
|
304
|
+
const methods = [...new Set(routeHits.map(r => r.match(/\.(get|post|put|patch|delete)/)?.[1]?.toUpperCase()))];
|
|
305
|
+
return cap(`API routes: ${methods.join(", ")} (${routeHits.length} endpoints)`);
|
|
306
|
+
}
|
|
307
|
+
// tRPC router
|
|
308
|
+
if (content.includes("createTRPCRouter") || content.includes("publicProcedure")) {
|
|
309
|
+
const procs = (content.match(/\.(query|mutation|subscription)\s*\(/g) || []).length;
|
|
310
|
+
return procs ? `tRPC router: ${procs} procedures` : "tRPC router";
|
|
311
|
+
}
|
|
312
|
+
// Zod schemas
|
|
313
|
+
if (content.includes("z.object") || content.includes("z.string")) {
|
|
314
|
+
const schemas = (content.match(/(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*z\./g) || [])
|
|
315
|
+
.map(s => s.match(/(?:const|let)\s+(\w+)/)?.[1]).filter(Boolean);
|
|
316
|
+
if (schemas.length)
|
|
317
|
+
return cap(`Zod schemas: ${schemas.slice(0, 4).join(", ")}${schemas.length > 4 ? ` + ${schemas.length - 4} more` : ""}`);
|
|
318
|
+
}
|
|
319
|
+
// Exports summary
|
|
320
|
+
const exports = (content.match(/export\s+(?:async\s+)?(?:function|class|const|interface|type|enum)\s+(\w+)/g) || [])
|
|
321
|
+
.map(e => e.match(/(\w+)$/)?.[1]).filter(Boolean);
|
|
322
|
+
if (exports.length > 0 && exports.length <= 5)
|
|
323
|
+
return `Exports ${exports.join(", ")}`;
|
|
324
|
+
if (exports.length > 5)
|
|
325
|
+
return cap(`Exports ${exports.slice(0, 4).join(", ")} + ${exports.length - 4} more`);
|
|
326
|
+
}
|
|
327
|
+
// ─── Python / Django / FastAPI / Flask ────────────────────
|
|
328
|
+
if (ext === ".py") {
|
|
329
|
+
// Django model
|
|
330
|
+
if (content.includes("models.Model")) {
|
|
331
|
+
const cls = content.match(/class\s+(\w+)\(.*models\.Model\)/);
|
|
332
|
+
const fields = (content.match(/^\s+\w+\s*=\s*models\.\w+/gm) || []).length;
|
|
333
|
+
return cap(`Model: ${cls?.[1] || "unknown"}, ${fields} fields`);
|
|
334
|
+
}
|
|
335
|
+
// FastAPI/Flask routes
|
|
336
|
+
if (content.includes("@router.") || content.includes("@app.")) {
|
|
337
|
+
const routes = (content.match(/@(?:router|app)\.(get|post|put|patch|delete)\s*\(/g) || []);
|
|
338
|
+
return cap(routes.length ? `API: ${routes.length} endpoints` : "API router");
|
|
339
|
+
}
|
|
340
|
+
// Pydantic
|
|
341
|
+
if (content.includes("BaseModel") && content.includes("Field(")) {
|
|
342
|
+
const cls = content.match(/class\s+(\w+)\(.*BaseModel\)/);
|
|
343
|
+
return cls ? `Pydantic: ${cls[1]}` : "Pydantic model";
|
|
344
|
+
}
|
|
345
|
+
// Celery
|
|
346
|
+
if (content.includes("@shared_task") || content.includes("@app.task")) {
|
|
347
|
+
const tasks = (content.match(/def\s+(\w+)/g) || []).map(m => m.match(/def\s+(\w+)/)?.[1]).filter(n => n && !n.startsWith("_"));
|
|
348
|
+
return cap(tasks.length ? `Celery tasks: ${tasks.join(", ")}` : "Celery task");
|
|
349
|
+
}
|
|
350
|
+
// Generic
|
|
351
|
+
const pyClass = content.match(/class\s+(\w+)/);
|
|
352
|
+
const funcs = (content.match(/def\s+(\w+)/g) || []).map(f => f.match(/def\s+(\w+)/)?.[1]).filter(n => n && !n.startsWith("_"));
|
|
353
|
+
if (pyClass && funcs.length > 0)
|
|
354
|
+
return cap(funcs.length > 4 ? `${pyClass[1]}: ${funcs.slice(0, 4).join(", ")} + ${funcs.length - 4} more` : `${pyClass[1]}: ${funcs.join(", ")}`);
|
|
355
|
+
if (funcs.length > 0)
|
|
356
|
+
return cap(funcs.slice(0, 4).join(", "));
|
|
357
|
+
}
|
|
358
|
+
// ─── Go ──────────────────────────────────────────────────
|
|
359
|
+
if (ext === ".go") {
|
|
360
|
+
const handlers = (content.match(/func\s+(\w+)\s*\(\s*\w+\s+http\.ResponseWriter/g) || [])
|
|
361
|
+
.map(m => m.match(/func\s+(\w+)/)?.[1]).filter(Boolean);
|
|
362
|
+
if (handlers.length)
|
|
363
|
+
return cap(`HTTP handlers: ${handlers.slice(0, 5).join(", ")}`);
|
|
364
|
+
const iface = content.match(/type\s+(\w+)\s+interface\s*\{/);
|
|
365
|
+
if (iface)
|
|
366
|
+
return `Interface: ${iface[1]}`;
|
|
367
|
+
const structM = content.match(/type\s+(\w+)\s+struct\s*\{/);
|
|
368
|
+
if (structM)
|
|
369
|
+
return `Struct: ${structM[1]}`;
|
|
370
|
+
const funcs = (content.match(/^func\s+(\w+)/gm) || []).map(m => m.match(/func\s+(\w+)/)?.[1]).filter(n => n && n[0] === n[0].toUpperCase());
|
|
371
|
+
if (funcs.length)
|
|
372
|
+
return cap(funcs.slice(0, 5).join(", "));
|
|
373
|
+
}
|
|
374
|
+
// ─── Rust ────────────────────────────────────────────────
|
|
375
|
+
if (ext === ".rs") {
|
|
376
|
+
const structM = content.match(/pub\s+struct\s+(\w+)/);
|
|
377
|
+
if (structM) {
|
|
378
|
+
const methods = (content.match(/pub\s+(?:async\s+)?fn\s+(\w+)/g) || []).map(m => m.match(/fn\s+(\w+)/)?.[1]).filter(Boolean);
|
|
379
|
+
return cap(methods.length ? `${structM[1]}: ${methods.slice(0, 4).join(", ")}` : `Struct: ${structM[1]}`);
|
|
380
|
+
}
|
|
381
|
+
const traitM = content.match(/pub\s+trait\s+(\w+)/);
|
|
382
|
+
if (traitM)
|
|
383
|
+
return `Trait: ${traitM[1]}`;
|
|
384
|
+
const enumM = content.match(/pub\s+enum\s+(\w+)/);
|
|
385
|
+
if (enumM)
|
|
386
|
+
return `Enum: ${enumM[1]}`;
|
|
387
|
+
const fns = (content.match(/pub\s+(?:async\s+)?fn\s+(\w+)/g) || []).map(m => m.match(/fn\s+(\w+)/)?.[1]).filter(Boolean);
|
|
388
|
+
if (fns.length)
|
|
389
|
+
return cap(fns.slice(0, 5).join(", "));
|
|
390
|
+
}
|
|
391
|
+
// ─── Java / Spring ───────────────────────────────────────
|
|
392
|
+
if (ext === ".java") {
|
|
393
|
+
const cls = content.match(/(?:public\s+)?class\s+(\w+)/);
|
|
394
|
+
const className = cls?.[1] || basename.replace(".java", "");
|
|
395
|
+
const annotations = (content.match(/@(RestController|Controller|Service|Repository|Component|Entity|Configuration)/g) || []).map(a => a.slice(1));
|
|
396
|
+
const mappings = (content.match(/@(?:Get|Post|Put|Patch|Delete|Request)Mapping/g) || []).length;
|
|
397
|
+
if (mappings)
|
|
398
|
+
return cap(`${annotations[0] || "Spring"}: ${className} (${mappings} endpoints)`);
|
|
399
|
+
if (annotations.length)
|
|
400
|
+
return `${annotations[0]}: ${className}`;
|
|
401
|
+
if (content.includes("@Entity"))
|
|
402
|
+
return `Entity: ${className}`;
|
|
403
|
+
const methods = (content.match(/public\s+(?:static\s+)?(?:\w+(?:<[\w,\s]+>)?)\s+(\w+)\s*\(/g) || [])
|
|
404
|
+
.map(m => m.match(/(\w+)\s*\(/)?.[1]).filter(n => n && n !== className);
|
|
405
|
+
if (methods.length)
|
|
406
|
+
return cap(`${className}: ${methods.slice(0, 4).join(", ")}`);
|
|
407
|
+
return className ? `Class: ${className}` : "";
|
|
408
|
+
}
|
|
409
|
+
// ─── Kotlin ──────────────────────────────────────────────
|
|
410
|
+
if (ext === ".kt" || ext === ".kts") {
|
|
411
|
+
const cls = content.match(/(?:data\s+)?class\s+(\w+)/);
|
|
412
|
+
if (content.match(/data\s+class/))
|
|
413
|
+
return `Data class: ${cls?.[1] || basename.replace(/\.kts?$/, "")}`;
|
|
414
|
+
if (content.includes("routing {"))
|
|
415
|
+
return "Ktor routing";
|
|
416
|
+
const fns = (content.match(/fun\s+(\w+)/g) || []).map(m => m.match(/fun\s+(\w+)/)?.[1]).filter(Boolean);
|
|
417
|
+
if (cls && fns.length)
|
|
418
|
+
return cap(`${cls[1]}: ${fns.slice(0, 4).join(", ")}`);
|
|
419
|
+
if (fns.length)
|
|
420
|
+
return cap(fns.slice(0, 5).join(", "));
|
|
421
|
+
}
|
|
422
|
+
// ─── C# / .NET ───────────────────────────────────────────
|
|
423
|
+
if (ext === ".cs") {
|
|
424
|
+
const cls = content.match(/(?:public\s+)?(?:partial\s+)?class\s+(\w+)(?:\s*:\s*(\w+))?/);
|
|
425
|
+
const className = cls?.[1] || basename.replace(".cs", "");
|
|
426
|
+
const parent = cls?.[2] || "";
|
|
427
|
+
if (parent === "Controller" || parent === "ControllerBase" || content.includes("[ApiController]")) {
|
|
428
|
+
const actions = (content.match(/\[Http(Get|Post|Put|Patch|Delete)\]/g) || []).map(a => a.match(/Http(\w+)/)?.[1]).filter(Boolean);
|
|
429
|
+
return cap(actions.length ? `API Controller: ${className} (${[...new Set(actions)].join(", ")})` : `Controller: ${className}`);
|
|
430
|
+
}
|
|
431
|
+
if (parent === "DbContext" || content.includes("DbSet<")) {
|
|
432
|
+
const sets = (content.match(/DbSet<(\w+)>/g) || []).map(s => s.match(/<(\w+)>/)?.[1]).filter(Boolean);
|
|
433
|
+
return cap(sets.length ? `DbContext: ${sets.join(", ")}` : `DbContext: ${className}`);
|
|
434
|
+
}
|
|
435
|
+
return className ? `Class: ${className}` : "";
|
|
436
|
+
}
|
|
437
|
+
// ─── Ruby / Rails ────────────────────────────────────────
|
|
438
|
+
if (ext === ".rb") {
|
|
439
|
+
const cls = content.match(/class\s+(\w+)(?:\s*<\s*(\w+(?:::\w+)?))?/);
|
|
440
|
+
const className = cls?.[1] || "";
|
|
441
|
+
const parent = cls?.[2] || "";
|
|
442
|
+
if (parent?.includes("Controller")) {
|
|
443
|
+
const actions = (content.match(/def\s+(index|show|new|create|edit|update|destroy|\w+)/g) || [])
|
|
444
|
+
.map(m => m.match(/def\s+(\w+)/)?.[1]).filter(n => n && !n.startsWith("_"));
|
|
445
|
+
return cap(actions.length ? `Controller: ${actions.join(", ")}` : `Controller: ${className}`);
|
|
446
|
+
}
|
|
447
|
+
if (parent === "ApplicationRecord" || parent === "ActiveRecord::Base")
|
|
448
|
+
return `Model: ${className}`;
|
|
449
|
+
if (basename.match(/^\d{14}_/)) {
|
|
450
|
+
const create = content.match(/create_table\s+:(\w+)/);
|
|
451
|
+
return create ? `Migration: create ${create[1]}` : "Database migration";
|
|
452
|
+
}
|
|
453
|
+
const methods = (content.match(/def\s+(\w+)/g) || []).map(m => m.match(/def\s+(\w+)/)?.[1]).filter(n => n && !n.startsWith("_"));
|
|
454
|
+
if (cls && methods.length)
|
|
455
|
+
return cap(`${className}: ${methods.slice(0, 4).join(", ")}`);
|
|
456
|
+
}
|
|
457
|
+
// ─── Swift ───────────────────────────────────────────────
|
|
458
|
+
if (ext === ".swift") {
|
|
459
|
+
if (content.includes(": View") || content.includes("some View")) {
|
|
460
|
+
const name = content.match(/struct\s+(\w+)\s*:\s*View/);
|
|
461
|
+
return name ? `SwiftUI view: ${name[1]}` : "SwiftUI view";
|
|
462
|
+
}
|
|
463
|
+
const proto = content.match(/protocol\s+(\w+)/);
|
|
464
|
+
if (proto)
|
|
465
|
+
return `Protocol: ${proto[1]}`;
|
|
466
|
+
const struct = content.match(/(?:public\s+)?struct\s+(\w+)/);
|
|
467
|
+
const cls = content.match(/(?:public\s+)?class\s+(\w+)/);
|
|
468
|
+
const name = struct?.[1] || cls?.[1] || "";
|
|
469
|
+
if (name)
|
|
470
|
+
return `${struct ? "Struct" : "Class"}: ${name}`;
|
|
471
|
+
}
|
|
472
|
+
// ─── Dart / Flutter ──────────────────────────────────────
|
|
473
|
+
if (ext === ".dart") {
|
|
474
|
+
if (content.includes("StatefulWidget") || content.includes("StatelessWidget")) {
|
|
475
|
+
const name = content.match(/class\s+(\w+)\s+extends\s+(?:Stateful|Stateless)Widget/);
|
|
476
|
+
return name ? `${content.includes("StatefulWidget") ? "Stateful" : "Stateless"} widget: ${name[1]}` : "Flutter widget";
|
|
477
|
+
}
|
|
478
|
+
const cls = content.match(/class\s+(\w+)/);
|
|
479
|
+
if (cls)
|
|
480
|
+
return `Class: ${cls[1]}`;
|
|
481
|
+
}
|
|
482
|
+
// ─── Vue / Svelte / Astro ────────────────────────────────
|
|
483
|
+
if (ext === ".vue") {
|
|
484
|
+
const name = content.match(/name:\s*['"]([^'"]+)['"]/);
|
|
485
|
+
const setup = content.includes("<script setup");
|
|
486
|
+
const parts = [];
|
|
487
|
+
if (name)
|
|
488
|
+
parts.push(name[1]);
|
|
489
|
+
if (setup)
|
|
490
|
+
parts.push("setup");
|
|
491
|
+
return cap(parts.length ? `Vue: ${parts.join(", ")}` : "Vue component");
|
|
492
|
+
}
|
|
493
|
+
if (ext === ".svelte")
|
|
494
|
+
return `Svelte: ${basename.replace(".svelte", "")}`;
|
|
495
|
+
if (ext === ".astro")
|
|
496
|
+
return `Astro: ${basename.replace(".astro", "")}`;
|
|
497
|
+
// ─── CSS / SCSS / Less ───────────────────────────────────
|
|
498
|
+
if (ext === ".css" || ext === ".scss" || ext === ".less") {
|
|
499
|
+
const rules = (content.match(/^[.#@][^\n{]+/gm) || []).length;
|
|
500
|
+
const vars = (content.match(/--[\w-]+\s*:/g) || []).length;
|
|
501
|
+
const parts = [];
|
|
502
|
+
if (rules)
|
|
503
|
+
parts.push(`${rules} rules`);
|
|
504
|
+
if (vars)
|
|
505
|
+
parts.push(`${vars} vars`);
|
|
506
|
+
return cap(parts.length ? `Styles: ${parts.join(", ")}` : "Stylesheet");
|
|
507
|
+
}
|
|
508
|
+
// ─── SQL ─────────────────────────────────────────────────
|
|
509
|
+
if (ext === ".sql") {
|
|
510
|
+
const creates = (content.match(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"']?(\w+)/gi) || [])
|
|
511
|
+
.map(m => m.match(/(?:TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?)([`"']?\w+)/i)?.[1]?.replace(/[`"']/g, "")).filter(Boolean);
|
|
512
|
+
if (creates.length)
|
|
513
|
+
return cap(`SQL: tables: ${creates.slice(0, 4).join(", ")}`);
|
|
514
|
+
}
|
|
515
|
+
// ─── Proto / GraphQL ─────────────────────────────────────
|
|
516
|
+
if (ext === ".proto") {
|
|
517
|
+
const msgs = (content.match(/message\s+(\w+)/g) || []).map(m => m.match(/message\s+(\w+)/)?.[1]).filter(Boolean);
|
|
518
|
+
const services = (content.match(/service\s+(\w+)/g) || []).map(m => m.match(/service\s+(\w+)/)?.[1]).filter(Boolean);
|
|
519
|
+
const parts = [];
|
|
520
|
+
if (msgs.length)
|
|
521
|
+
parts.push(`messages: ${msgs.slice(0, 3).join(", ")}`);
|
|
522
|
+
if (services.length)
|
|
523
|
+
parts.push(`services: ${services.join(", ")}`);
|
|
524
|
+
return cap(parts.length ? `Proto: ${parts.join(", ")}` : "");
|
|
525
|
+
}
|
|
526
|
+
if (ext === ".graphql" || ext === ".gql") {
|
|
527
|
+
const types = (content.match(/type\s+(\w+)/g) || []).map(m => m.match(/type\s+(\w+)/)?.[1]).filter(Boolean);
|
|
528
|
+
return cap(types.length ? `GraphQL: types: ${types.slice(0, 4).join(", ")}` : "GraphQL schema");
|
|
529
|
+
}
|
|
530
|
+
// ─── YAML ────────────────────────────────────────────────
|
|
531
|
+
if (ext === ".yaml" || ext === ".yml") {
|
|
532
|
+
if (content.includes("runs-on:")) {
|
|
533
|
+
const name = content.match(/^name:\s*(.+)$/m);
|
|
534
|
+
return cap(name ? `CI: ${name[1].trim()}` : "GitHub Actions workflow");
|
|
535
|
+
}
|
|
536
|
+
if (content.includes("apiVersion:") && content.includes("kind:")) {
|
|
537
|
+
const kind = content.match(/kind:\s*(\w+)/);
|
|
538
|
+
return cap(kind ? `K8s ${kind[1]}` : "Kubernetes manifest");
|
|
539
|
+
}
|
|
540
|
+
if (content.includes("services:") && (basename.includes("docker") || basename.includes("compose"))) {
|
|
541
|
+
const services = (content.match(/^\s{2}\w+:/gm) || []).length;
|
|
542
|
+
return `Docker Compose: ${services} services`;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
// ─── TOML ────────────────────────────────────────────────
|
|
546
|
+
if (ext === ".toml") {
|
|
547
|
+
const desc = content.match(/^description\s*=\s*"([^"]+)"/m);
|
|
548
|
+
if (desc)
|
|
549
|
+
return cap(desc[1]);
|
|
550
|
+
}
|
|
551
|
+
// ─── Elixir ──────────────────────────────────────────────
|
|
552
|
+
if (ext === ".ex" || ext === ".exs") {
|
|
553
|
+
const mod = content.match(/defmodule\s+([\w.]+)/);
|
|
554
|
+
if (content.includes("Phoenix.LiveView"))
|
|
555
|
+
return cap(mod ? `LiveView: ${mod[1]}` : "Phoenix LiveView");
|
|
556
|
+
if (content.includes("Controller"))
|
|
557
|
+
return cap(mod ? `Phoenix controller: ${mod[1]}` : "Phoenix controller");
|
|
558
|
+
const fns = (content.match(/def\s+(\w+)/g) || []).map(m => m.match(/def\s+(\w+)/)?.[1]).filter(Boolean);
|
|
559
|
+
if (mod && fns.length)
|
|
560
|
+
return cap(`${mod[1]}: ${fns.slice(0, 4).join(", ")}`);
|
|
561
|
+
if (mod)
|
|
562
|
+
return mod[1];
|
|
563
|
+
}
|
|
564
|
+
// ─── Lua ─────────────────────────────────────────────────
|
|
565
|
+
if (ext === ".lua") {
|
|
566
|
+
const fns = (content.match(/function\s+(?:\w+[.:])?(\w+)/g) || []).map(m => m.match(/(\w+)\s*$/)?.[1]).filter(Boolean);
|
|
567
|
+
if (fns.length)
|
|
568
|
+
return cap(fns.slice(0, 5).join(", "));
|
|
569
|
+
}
|
|
570
|
+
// ─── Zig ─────────────────────────────────────────────────
|
|
571
|
+
if (ext === ".zig") {
|
|
572
|
+
const fns = (content.match(/pub\s+fn\s+(\w+)/g) || []).map(m => m.match(/fn\s+(\w+)/)?.[1]).filter(Boolean);
|
|
573
|
+
if (fns.length)
|
|
574
|
+
return cap(fns.slice(0, 5).join(", "));
|
|
575
|
+
}
|
|
576
|
+
// Last resort
|
|
577
|
+
const declM = content.match(/(?:function|class|const|interface|type|enum)\s+(\w+)/);
|
|
578
|
+
if (declM) {
|
|
579
|
+
const name = declM[1];
|
|
580
|
+
const methods = (content.match(/(?:public\s+)?(?:async\s+)?(?:function\s+|(?:get|set)\s+)(\w+)\s*\(/g) || [])
|
|
581
|
+
.map(m => m.match(/(\w+)\s*\(/)?.[1]).filter(n => n && n !== name && n !== "__construct" && n !== "constructor");
|
|
582
|
+
if (methods.length > 0 && methods.length <= 5)
|
|
583
|
+
return cap(`${name}: ${methods.join(", ")}`);
|
|
584
|
+
if (methods.length > 5)
|
|
585
|
+
return cap(`${name}: ${methods.slice(0, 3).join(", ")} + ${methods.length - 3} more`);
|
|
586
|
+
return `Declares ${name}`;
|
|
587
|
+
}
|
|
588
|
+
return "";
|
|
589
|
+
}
|
|
590
|
+
export function estimateTokens(text, type = "mixed") {
|
|
591
|
+
const ratio = type === "code" ? 3.5 : type === "prose" ? 4.0 : 3.75;
|
|
592
|
+
return Math.ceil(text.length / ratio);
|
|
593
|
+
}
|
|
594
|
+
export function timestamp() {
|
|
595
|
+
return new Date().toISOString();
|
|
596
|
+
}
|
|
597
|
+
export function timeShort() {
|
|
598
|
+
const d = new Date();
|
|
599
|
+
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
600
|
+
}
|
|
601
|
+
export function readStdin() {
|
|
602
|
+
return new Promise((resolve) => {
|
|
603
|
+
const chunks = [];
|
|
604
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
605
|
+
process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
606
|
+
// If no stdin data after 4s, resolve with whatever we have so far.
|
|
607
|
+
// On Windows, stdin delivery from Claude Code hooks can be slow.
|
|
608
|
+
setTimeout(() => resolve(chunks.length ? Buffer.concat(chunks).toString("utf-8") : "{}"), 4000);
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
export function normalizePath(p) {
|
|
612
|
+
return p.replace(/\\/g, "/");
|
|
613
|
+
}
|
|
614
|
+
//# sourceMappingURL=shared.js.map
|