@qloo/qloo-harness 0.1.18

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.
@@ -0,0 +1,364 @@
1
+ import { createRequire as __qlooCreateRequire } from "node:module";
2
+ const require = __qlooCreateRequire(import.meta.url);
3
+
4
+ // apps/qloo-harness/dist/project-context.js
5
+ import { createHash } from "node:crypto";
6
+ import { lstat, mkdir, readFile, rename, writeFile } from "node:fs/promises";
7
+ import { basename, dirname, join, resolve } from "node:path";
8
+ import { Type } from "typebox";
9
+ var QLOO_PROJECT_CONTEXT_SCHEMA_VERSION = "1.1";
10
+ var MAX_MANIFEST_BYTES = 256 * 1024;
11
+ var CANDIDATE_FILES = [
12
+ "package.json",
13
+ "package-lock.json",
14
+ "pnpm-lock.yaml",
15
+ "yarn.lock",
16
+ "bun.lock",
17
+ "pyproject.toml",
18
+ "requirements.txt",
19
+ "uv.lock",
20
+ "poetry.lock",
21
+ "go.mod",
22
+ "Cargo.toml",
23
+ "pom.xml",
24
+ "build.gradle",
25
+ "build.gradle.kts",
26
+ "Dockerfile",
27
+ "docker-compose.yml",
28
+ "docker-compose.yaml",
29
+ "compose.yml",
30
+ "compose.yaml",
31
+ "serverless.yml",
32
+ "serverless.yaml"
33
+ ];
34
+ var CANDIDATE_MARKER_FILES = [".env.example"];
35
+ var CANDIDATE_SOURCE_ROOTS = [
36
+ "app",
37
+ "apps",
38
+ "cmd",
39
+ "infra",
40
+ "lib",
41
+ "packages",
42
+ "src",
43
+ "test",
44
+ "tests"
45
+ ];
46
+ var FRAMEWORK_NAMES = [
47
+ "@aws-sdk",
48
+ "@modelcontextprotocol",
49
+ "angular",
50
+ "aws-cdk",
51
+ "django",
52
+ "express",
53
+ "fastapi",
54
+ "fastify",
55
+ "flask",
56
+ "hono",
57
+ "nestjs",
58
+ "next",
59
+ "react",
60
+ "serverless",
61
+ "svelte",
62
+ "terraform",
63
+ "vue"
64
+ ];
65
+ var CAPABILITY_DEPENDENCIES = {
66
+ http_clients: [
67
+ "aiohttp",
68
+ "axios",
69
+ "got",
70
+ "httpx",
71
+ "node-fetch",
72
+ "requests",
73
+ "undici"
74
+ ],
75
+ data_modeling: [
76
+ "@sinclair/typebox",
77
+ "mongoose",
78
+ "prisma",
79
+ "pydantic",
80
+ "sqlalchemy",
81
+ "typebox",
82
+ "zod"
83
+ ],
84
+ configuration: [
85
+ "config",
86
+ "dotenv",
87
+ "dynaconf",
88
+ "pydantic-settings"
89
+ ],
90
+ test_frameworks: [
91
+ "ava",
92
+ "jest",
93
+ "mocha",
94
+ "playwright",
95
+ "pytest",
96
+ "vitest"
97
+ ]
98
+ };
99
+ async function readCandidate(cwd, name) {
100
+ const path = join(cwd, name);
101
+ try {
102
+ const linkMetadata = await lstat(path);
103
+ if (!linkMetadata.isFile() || linkMetadata.isSymbolicLink())
104
+ return void 0;
105
+ const metadata = linkMetadata;
106
+ if (metadata.size > MAX_MANIFEST_BYTES) {
107
+ return {
108
+ name,
109
+ size: metadata.size,
110
+ modifiedMs: metadata.mtimeMs,
111
+ content: ""
112
+ };
113
+ }
114
+ return {
115
+ name,
116
+ size: metadata.size,
117
+ modifiedMs: metadata.mtimeMs,
118
+ content: await readFile(path, "utf8")
119
+ };
120
+ } catch {
121
+ return void 0;
122
+ }
123
+ }
124
+ async function readMarker(cwd, name, kind) {
125
+ try {
126
+ const metadata = await lstat(join(cwd, name));
127
+ if (metadata.isSymbolicLink())
128
+ return void 0;
129
+ if (kind === "file" ? !metadata.isFile() : !metadata.isDirectory())
130
+ return void 0;
131
+ return { name, kind, modifiedMs: metadata.mtimeMs };
132
+ } catch {
133
+ return void 0;
134
+ }
135
+ }
136
+ function sorted(values) {
137
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
138
+ }
139
+ function packageMetadata(packageJson) {
140
+ try {
141
+ const parsed = JSON.parse(packageJson);
142
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
143
+ return { dependencies: [], scripts: [], scriptCommands: {} };
144
+ }
145
+ const manifest = parsed;
146
+ const dependencies = [
147
+ "dependencies",
148
+ "devDependencies",
149
+ "peerDependencies",
150
+ "optionalDependencies"
151
+ ].flatMap((key) => {
152
+ const value = manifest[key];
153
+ return value && typeof value === "object" && !Array.isArray(value) ? Object.keys(value) : [];
154
+ });
155
+ const scriptValue = manifest.scripts;
156
+ const scriptCommands = scriptValue && typeof scriptValue === "object" && !Array.isArray(scriptValue) ? Object.fromEntries(Object.entries(scriptValue).filter((entry) => typeof entry[1] === "string")) : {};
157
+ return {
158
+ dependencies,
159
+ scripts: Object.keys(scriptCommands),
160
+ scriptCommands
161
+ };
162
+ } catch {
163
+ return { dependencies: [], scripts: [], scriptCommands: {} };
164
+ }
165
+ }
166
+ function pythonDependencyNames(files) {
167
+ const content = files.filter(({ name }) => name === "pyproject.toml" || name === "requirements.txt").map(({ content: value }) => value).join("\n").toLocaleLowerCase("en-US");
168
+ const known = [
169
+ ...FRAMEWORK_NAMES,
170
+ ...Object.values(CAPABILITY_DEPENDENCIES).flat(),
171
+ "qloo",
172
+ "qloo-client",
173
+ "qloo-mcp"
174
+ ];
175
+ return sorted(known.filter((name) => {
176
+ const normalized = name.toLocaleLowerCase("en-US");
177
+ const escaped = normalized.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
178
+ return new RegExp(`(^|[^a-z0-9_.-])${escaped}([^a-z0-9_.-]|$)`, "u").test(content);
179
+ }));
180
+ }
181
+ function detectedCapabilities(names, capability) {
182
+ const normalized = new Set(names.map((name) => name.toLocaleLowerCase("en-US")));
183
+ return sorted(CAPABILITY_DEPENDENCIES[capability].filter((name) => normalized.has(name)));
184
+ }
185
+ function verificationCommands(packageManager, metadata) {
186
+ const relevant = metadata.scripts.filter((name) => /^(?:build|check|lint|test|typecheck)(?::[a-z0-9:_-]+)?$/u.test(name));
187
+ const prefix = packageManager === "pnpm" ? "pnpm" : packageManager === "Yarn" ? "yarn" : packageManager === "Bun" ? "bun run" : "npm run";
188
+ const packageCommands = relevant.map((name) => packageManager === "npm" && name === "test" ? "npm test" : `${prefix} ${name}`);
189
+ const scriptBodies = Object.values(metadata.scriptCommands).join("\n").toLocaleLowerCase("en-US");
190
+ const inferred = [
191
+ scriptBodies.includes("node --test") ? "Node test runner" : void 0,
192
+ scriptBodies.includes("tsc ") || scriptBodies.startsWith("tsc") ? "TypeScript compiler" : void 0
193
+ ].filter((value) => value !== void 0);
194
+ return sorted([...packageCommands, ...inferred]);
195
+ }
196
+ function packageProjectName(packageJson) {
197
+ try {
198
+ const parsed = JSON.parse(packageJson);
199
+ const name = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? Reflect.get(parsed, "name") : void 0;
200
+ return typeof name === "string" && name.trim() ? name.trim() : void 0;
201
+ } catch {
202
+ return void 0;
203
+ }
204
+ }
205
+ function pythonProjectName(pyproject) {
206
+ const match = pyproject.match(/^name\s*=\s*["']([^"']+)["']/mu);
207
+ return match?.[1]?.trim() || void 0;
208
+ }
209
+ function detectContext(cwd, files, markers, now) {
210
+ const byName = new Map(files.map((file) => [file.name, file]));
211
+ const markerFiles = markers.filter(({ kind }) => kind === "file");
212
+ const sourceRoots = markers.filter(({ kind }) => kind === "directory").map(({ name }) => name);
213
+ const detectedNames = sorted([
214
+ ...files.map(({ name }) => name),
215
+ ...markerFiles.map(({ name }) => name)
216
+ ]);
217
+ const languages = /* @__PURE__ */ new Set();
218
+ const packageManagers = /* @__PURE__ */ new Set();
219
+ const infrastructure = /* @__PURE__ */ new Set();
220
+ if (byName.has("package.json"))
221
+ languages.add("JavaScript/TypeScript");
222
+ if (byName.has("package-lock.json"))
223
+ packageManagers.add("npm");
224
+ if (byName.has("pnpm-lock.yaml"))
225
+ packageManagers.add("pnpm");
226
+ if (byName.has("yarn.lock"))
227
+ packageManagers.add("Yarn");
228
+ if (byName.has("bun.lock"))
229
+ packageManagers.add("Bun");
230
+ if (byName.has("pyproject.toml") || byName.has("requirements.txt"))
231
+ languages.add("Python");
232
+ if (byName.has("uv.lock"))
233
+ packageManagers.add("uv");
234
+ if (byName.has("poetry.lock"))
235
+ packageManagers.add("Poetry");
236
+ if (byName.has("go.mod"))
237
+ languages.add("Go");
238
+ if (byName.has("Cargo.toml"))
239
+ languages.add("Rust");
240
+ if (byName.has("pom.xml") || byName.has("build.gradle") || byName.has("build.gradle.kts")) {
241
+ languages.add("JVM");
242
+ }
243
+ if (detectedNames.some((name) => name === "Dockerfile" || name.includes("compose"))) {
244
+ infrastructure.add("Docker");
245
+ }
246
+ if (detectedNames.some((name) => name.startsWith("serverless."))) {
247
+ infrastructure.add("Serverless Framework");
248
+ }
249
+ const packageContent = byName.get("package.json")?.content ?? "";
250
+ const packageInfo = packageMetadata(packageContent);
251
+ const pythonDependencies = pythonDependencyNames(files);
252
+ const dependencies = sorted([...packageInfo.dependencies, ...pythonDependencies]);
253
+ const searchable = `${dependencies.join("\n")}
254
+ ${files.map(({ content }) => content).join("\n")}`.toLocaleLowerCase("en-US");
255
+ const frameworks = FRAMEWORK_NAMES.filter((name) => searchable.includes(name));
256
+ const qlooDependencies = sorted(dependencies.filter((name) => name.toLowerCase().includes("qloo")));
257
+ const httpClients = detectedCapabilities(dependencies, "http_clients");
258
+ const dataModeling = detectedCapabilities(dependencies, "data_modeling");
259
+ const testFrameworks = detectedCapabilities(dependencies, "test_frameworks");
260
+ const configuration = detectedCapabilities(dependencies, "configuration");
261
+ if (markerFiles.some(({ name }) => name === ".env.example")) {
262
+ configuration.push("environment template");
263
+ }
264
+ const jsPackageManager = packageManagers.has("pnpm") ? "pnpm" : packageManagers.has("Yarn") ? "Yarn" : packageManagers.has("Bun") ? "Bun" : packageManagers.has("npm") ? "npm" : void 0;
265
+ const verificationScripts = verificationCommands(jsPackageManager, packageInfo);
266
+ if (testFrameworks.includes("pytest"))
267
+ verificationScripts.push("pytest");
268
+ const fingerprint = createHash("sha256").update([
269
+ ...files.map(({ name, size, modifiedMs }) => `file:${name}:${size}:${modifiedMs}`),
270
+ ...markers.map(({ kind, name, modifiedMs }) => `${kind}:${name}:${modifiedMs}`)
271
+ ].join("\n")).digest("hex");
272
+ const projectName = packageProjectName(packageContent) ?? pythonProjectName(byName.get("pyproject.toml")?.content ?? "") ?? basename(cwd);
273
+ return {
274
+ schema_version: QLOO_PROJECT_CONTEXT_SCHEMA_VERSION,
275
+ project_name: projectName,
276
+ project_root: cwd,
277
+ fingerprint,
278
+ generated_at: now.toISOString(),
279
+ languages: sorted(languages),
280
+ package_managers: sorted(packageManagers),
281
+ frameworks: sorted(frameworks),
282
+ infrastructure: sorted(infrastructure),
283
+ source_roots: sorted(sourceRoots),
284
+ http_clients: httpClients,
285
+ data_modeling: dataModeling,
286
+ configuration: sorted(configuration),
287
+ test_frameworks: testFrameworks,
288
+ verification_scripts: sorted(verificationScripts),
289
+ qloo_dependencies: qlooDependencies,
290
+ detected_files: detectedNames
291
+ };
292
+ }
293
+ async function readCachedContext(path) {
294
+ try {
295
+ const parsed = JSON.parse(await readFile(path, "utf8"));
296
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || Reflect.get(parsed, "schema_version") !== QLOO_PROJECT_CONTEXT_SCHEMA_VERSION)
297
+ return void 0;
298
+ return parsed;
299
+ } catch {
300
+ return void 0;
301
+ }
302
+ }
303
+ async function writeCachedContext(path, context) {
304
+ const temporaryPath = `${path}.${process.pid}.tmp`;
305
+ await mkdir(dirname(path), { recursive: true, mode: 448 });
306
+ await writeFile(temporaryPath, `${JSON.stringify(context, null, 2)}
307
+ `, {
308
+ encoding: "utf8",
309
+ mode: 384
310
+ });
311
+ await rename(temporaryPath, path);
312
+ }
313
+ async function inspectProjectContext(options) {
314
+ const cwd = resolve(options.cwd);
315
+ const candidates = (await Promise.all(CANDIDATE_FILES.map((name) => readCandidate(cwd, name)))).filter((value) => value !== void 0);
316
+ const markers = (await Promise.all([
317
+ ...CANDIDATE_MARKER_FILES.map((name) => readMarker(cwd, name, "file")),
318
+ ...CANDIDATE_SOURCE_ROOTS.map((name) => readMarker(cwd, name, "directory"))
319
+ ])).filter((value) => value !== void 0);
320
+ const detected = detectContext(cwd, candidates, markers, (options.now ?? (() => /* @__PURE__ */ new Date()))());
321
+ if (!options.refresh) {
322
+ const cached = await readCachedContext(options.paths.projectContextFile);
323
+ if (cached?.project_root === cwd && cached.fingerprint === detected.fingerprint) {
324
+ return { cache: "hit", context: cached };
325
+ }
326
+ }
327
+ await writeCachedContext(options.paths.projectContextFile, detected);
328
+ return { cache: "refreshed", context: detected };
329
+ }
330
+ function createProjectContextTool(options) {
331
+ const parameters = Type.Object({
332
+ refresh: Type.Optional(Type.Boolean({ default: false }))
333
+ }, { additionalProperties: false });
334
+ const tool = {
335
+ name: "qloo_project_context",
336
+ label: "Qloo project context",
337
+ description: "Inspect bounded, non-secret project manifests and structural markers to identify integration boundaries, HTTP clients, data modeling, configuration, and verification commands without reading application source or environment values.",
338
+ promptSnippet: "Inspect the current project's integration context",
339
+ promptGuidelines: [
340
+ "Use this once near the start of an integration or planning request.",
341
+ "Use the result to map local domain inputs to Qloo signals and filters, then map normalized Qloo results and errors back to the project's data model and HTTP boundary.",
342
+ "Treat source_roots as locations to inspect with read-only workspace tools; this tool deliberately does not read their contents.",
343
+ "Use refresh only after relevant project manifests changed."
344
+ ],
345
+ parameters,
346
+ async execute(_toolCallId, params) {
347
+ const result = await inspectProjectContext({
348
+ cwd: options.cwd,
349
+ paths: options.paths,
350
+ refresh: params.refresh ?? false
351
+ });
352
+ return {
353
+ content: [{ type: "text", text: JSON.stringify(result) }],
354
+ details: result
355
+ };
356
+ }
357
+ };
358
+ return tool;
359
+ }
360
+ export {
361
+ QLOO_PROJECT_CONTEXT_SCHEMA_VERSION,
362
+ createProjectContextTool,
363
+ inspectProjectContext
364
+ };