Skip to content

Commit bedb560

Browse files
committed
fix packaging types and add lint/format quality gates
1 parent 2a884da commit bedb560

11 files changed

Lines changed: 115 additions & 27 deletions

File tree

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ jobs:
2727
- name: Install dependencies
2828
run: bun install --frozen-lockfile
2929

30+
- name: Lint
31+
run: bun run lint
32+
33+
- name: Format check
34+
run: bun run format:check
35+
3036
- name: Build
3137
run: bun run build
3238

.github/workflows/release.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ jobs:
3232
- name: Install dependencies
3333
run: bun install --frozen-lockfile
3434

35+
- name: Lint
36+
run: bun run lint
37+
38+
- name: Format check
39+
run: bun run format:check
40+
3541
- name: Build
3642
run: bun run build
3743

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,4 @@ tests/yaml_complex_test.js
162162
examples/express-integration.js
163163
examples/express-integration.ts
164164
Qirrel.md
165+
.tmp/

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [QIRRELCONTEXT IMPLEMENTATION 0.2.0]
99

1010
### Added
11-
- QirrelContext as the canonical context
11+
- QirrelContext as the canonical context
1212
- Namespaced fields: meta (requestId, timestamp, source, trace), memory (shortTerm, longTerm, cache), and llm (model, temperature, safety)
1313
- JSON serializability support for context persistence and transport
1414
- Backward compatibility safeguards for existing integrations

package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
"types": "./dist/index.d.ts",
77
"scripts": {
88
"build": "tsc",
9+
"typecheck": "tsc --noEmit",
10+
"lint": "bun run typecheck",
11+
"format:check": "node scripts/check-format.mjs",
912
"dev": "tsc --watch",
1013
"test": "jest",
1114
"test:watch": "jest --watch",
@@ -14,7 +17,7 @@
1417
"bench:frameworks": "bun run bench/framework-comparison.bench.ts",
1518
"bench:report": "bun run bench/report.bench.ts",
1619
"mcp:start": "bun run src/bin/qirrel-mcp.ts",
17-
"prepublishOnly": "npm run build"
20+
"prepublishOnly": "bun run lint && bun run format:check && bun run build"
1821
},
1922
"bin": {
2023
"qirrel-mcp": "./dist/bin/qirrel-mcp.js"
@@ -40,18 +43,17 @@
4043
},
4144
"homepage": "https://github.com/dev-dami/qirrel#readme",
4245
"dependencies": {
43-
"@types/node": "^24.10.9",
4446
"compromise": "^14.14.5",
4547
"emoji-regex": "^10.6.0",
4648
"js-yaml": "^4.1.1",
4749
"libphonenumber-js": "^1.12.31",
4850
"lru-cache": "^11.2.4",
4951
"tokenizers": "^0.13.3",
50-
"typescript": "^5.9.3",
5152
"uuid": "^13.0.0",
5253
"validator": "^13.15.23"
5354
},
5455
"devDependencies": {
56+
"@types/node": "^24.10.9",
5557
"@types/emoji-regex": "^9.2.2",
5658
"@types/jest": "^30.0.0",
5759
"@types/js-yaml": "^4.0.9",
@@ -62,6 +64,7 @@
6264
"langchain": "^1.0.2",
6365
"tinybench": "^2.9.0",
6466
"ts-jest": "^29.4.6",
67+
"typescript": "^5.9.3",
6568
"zod": "^4.1.5"
6669
},
6770
"files": [

scripts/check-format.mjs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env node
2+
3+
import { readdirSync, readFileSync } from "node:fs";
4+
import { join, relative } from "node:path";
5+
6+
const ROOT = process.cwd();
7+
const EXCLUDED_DIRS = new Set([
8+
".git",
9+
"node_modules",
10+
"dist",
11+
"coverage",
12+
".tmp",
13+
]);
14+
const EXCLUDED_FILES = new Set(["bun.lock", "package-lock.json"]);
15+
const CHECKED_EXTENSIONS = new Set([
16+
".ts",
17+
".tsx",
18+
".js",
19+
".cjs",
20+
".mjs",
21+
".json",
22+
".md",
23+
".yml",
24+
".yaml",
25+
]);
26+
27+
function walk(dir, out) {
28+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
29+
const fullPath = join(dir, entry.name);
30+
if (entry.isDirectory()) {
31+
if (!EXCLUDED_DIRS.has(entry.name)) walk(fullPath, out);
32+
continue;
33+
}
34+
if (!entry.isFile()) continue;
35+
if (EXCLUDED_FILES.has(entry.name)) continue;
36+
const ext = entry.name.includes(".")
37+
? `.${entry.name.split(".").pop().toLowerCase()}`
38+
: "";
39+
if (CHECKED_EXTENSIONS.has(ext)) out.push(fullPath);
40+
}
41+
}
42+
43+
const files = [];
44+
walk(ROOT, files);
45+
46+
const failures = [];
47+
for (const filePath of files) {
48+
const relativePath = relative(ROOT, filePath);
49+
const content = readFileSync(filePath, "utf8");
50+
const lines = content.split("\n");
51+
52+
for (let i = 0; i < lines.length; i++) {
53+
const line = lines[i];
54+
if (line.includes("\r")) {
55+
failures.push(`${relativePath}:${i + 1} contains CRLF characters`);
56+
}
57+
if (/\t/.test(line)) {
58+
failures.push(`${relativePath}:${i + 1} contains tab indentation`);
59+
}
60+
if (/[ \t]+$/.test(line)) {
61+
failures.push(`${relativePath}:${i + 1} has trailing whitespace`);
62+
}
63+
}
64+
}
65+
66+
if (failures.length > 0) {
67+
console.error("format:check failed");
68+
for (const failure of failures) console.error(`- ${failure}`);
69+
process.exit(1);
70+
}
71+
72+
console.log("format:check passed");

tests/events.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ describe('Pipeline Events', () => {
1313
pipeline.on(PipelineEvent.RunStart, (payload) => {
1414
events.push({ event: PipelineEvent.RunStart, payload });
1515
});
16-
16+
1717
pipeline.on(PipelineEvent.RunEnd, (payload) => {
1818
events.push({ event: PipelineEvent.RunEnd, payload });
1919
});
@@ -31,7 +31,7 @@ describe('Pipeline Events', () => {
3131
pipeline.on(PipelineEvent.ProcessorStart, (payload) => {
3232
events.push({ event: PipelineEvent.ProcessorStart, payload });
3333
});
34-
34+
3535
pipeline.on(PipelineEvent.ProcessorEnd, (payload) => {
3636
events.push({ event: PipelineEvent.ProcessorEnd, payload });
3737
});
@@ -47,10 +47,10 @@ describe('Pipeline Events', () => {
4747
test('should allow removing event listeners', async () => {
4848
const handler = jest.fn();
4949
pipeline.on(PipelineEvent.RunStart, handler);
50-
50+
5151
await pipeline.process("Hello world!");
5252
expect(handler).toHaveBeenCalledTimes(1);
53-
53+
5454
pipeline.off(PipelineEvent.RunStart, handler);
5555
await pipeline.process("Hello again!");
5656
expect(handler).toHaveBeenCalledTimes(1); // Should not be called again
@@ -59,10 +59,10 @@ describe('Pipeline Events', () => {
5959
test('should handle multiple event listeners', async () => {
6060
const handler1 = jest.fn();
6161
const handler2 = jest.fn();
62-
62+
6363
pipeline.on(PipelineEvent.RunStart, handler1);
6464
pipeline.on(PipelineEvent.RunStart, handler2);
65-
65+
6666
await pipeline.process("Hello world!");
6767

6868
expect(handler1).toHaveBeenCalledTimes(1);
@@ -90,11 +90,11 @@ describe('Pipeline Events', () => {
9090
test('should have correct event payload structure', async () => {
9191
let runStartPayload: any;
9292
let processorStartPayload: any;
93-
93+
9494
pipeline.on(PipelineEvent.RunStart, (payload) => {
9595
runStartPayload = payload;
9696
});
97-
97+
9898
pipeline.on(PipelineEvent.ProcessorStart, (payload) => {
9999
processorStartPayload = payload;
100100
});

tests/llms-base.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ describe('BaseLLMAdapter', () => {
3737
it('should initialize with default values when not provided', () => {
3838
const minimalConfig: LLMConfig = { apiKey: 'test-key' };
3939
const minimalAdapter = new TestLLMAdapter(minimalConfig);
40-
40+
4141
expect(minimalAdapter).toBeDefined();
4242
});
4343

@@ -419,7 +419,7 @@ describe('BaseLLMAdapter', () => {
419419
describe('generateWithCache', () => {
420420
it('should cache responses', async () => {
421421
const adapterWithCache = new TestLLMAdapter(defaultConfig, true);
422-
422+
423423
const result1 = await (adapterWithCache as any).generateWithCache('test prompt');
424424
const result2 = await (adapterWithCache as any).generateWithCache('test prompt');
425425

@@ -428,7 +428,7 @@ describe('BaseLLMAdapter', () => {
428428

429429
it('should work without cache', async () => {
430430
const adapterNoCache = new TestLLMAdapter(defaultConfig, false);
431-
431+
432432
const result = await (adapterNoCache as any).generateWithCache('test prompt');
433433

434434
expect(result).toBeDefined();
@@ -476,7 +476,7 @@ describe('BaseLLMAdapter', () => {
476476

477477
it('should store response with custom TTL from options', async () => {
478478
const adapterWithCache = new TestLLMAdapter(defaultConfig, true);
479-
479+
480480
await (adapterWithCache as any).generateWithCache('test', { timeout: 600000 });
481481

482482
// Response should be cached

tests/llms-openai.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ describe('OpenAILLMAdapter', () => {
228228

229229
it('should log error to console when response has no content', async () => {
230230
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
231-
231+
232232
(global.fetch as jest.Mock).mockResolvedValue({
233233
ok: true,
234234
json: async () => ({
@@ -295,7 +295,7 @@ describe('OpenAILLMAdapter', () => {
295295
it('should handle AbortError specifically', async () => {
296296
const abortError = new Error('Aborted');
297297
abortError.name = 'AbortError';
298-
298+
299299
(global.fetch as jest.Mock).mockRejectedValue(abortError);
300300

301301
await expect(adapter.generate('Test')).rejects.toThrow('Request timed out');
@@ -304,7 +304,7 @@ describe('OpenAILLMAdapter', () => {
304304
it('should sanitize error messages', async () => {
305305
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
306306
const testError = new Error('Sensitive error details');
307-
307+
308308
(global.fetch as jest.Mock).mockRejectedValue(testError);
309309

310310
await expect(adapter.generate('Test')).rejects.toThrow(
@@ -320,7 +320,7 @@ describe('OpenAILLMAdapter', () => {
320320

321321
it('should handle non-Error exceptions', async () => {
322322
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
323-
323+
324324
(global.fetch as jest.Mock).mockRejectedValue('String error');
325325

326326
await expect(adapter.generate('Test')).rejects.toThrow(
@@ -337,7 +337,7 @@ describe('OpenAILLMAdapter', () => {
337337
it('should include error name in sanitized message for Error objects', async () => {
338338
jest.spyOn(console, 'error').mockImplementation();
339339
const testError = new TypeError('Type error occurred');
340-
340+
341341
(global.fetch as jest.Mock).mockRejectedValue(testError);
342342

343343
await expect(adapter.generate('Test')).rejects.toThrow('TypeError');
@@ -472,7 +472,7 @@ describe('OpenAILLMAdapter', () => {
472472
describe('error logging', () => {
473473
it('should use console.error instead of console.warn for missing content', async () => {
474474
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
475-
475+
476476
(global.fetch as jest.Mock).mockResolvedValue({
477477
ok: true,
478478
json: async () => ({

tests/speech.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ describe('Speech Processing Functions', () => {
6363
describe('analyzeSpeechPatterns', () => {
6464
it('should detect filler words', () => {
6565
const result = analyzeSpeechPatterns('Well, um, I think like basically it works');
66-
66+
6767
expect(result.fillerWords).toContain('Well,');
6868
expect(result.fillerWords).toContain('um,');
6969
expect(result.fillerWords).toContain('like');
@@ -78,22 +78,22 @@ describe('Speech Processing Functions', () => {
7878

7979
it('should detect stutters', () => {
8080
const result = analyzeSpeechPatterns('I am n-nervous and s-stuttering');
81-
81+
8282
expect(result.stutters).toContain('n-nervous');
8383
expect(result.stutters).toContain('s-stuttering');
8484
});
8585

8686
it('should handle mixed speech patterns', () => {
8787
const result = analyzeSpeechPatterns('Well, I think I think this is um, n-nervous');
88-
88+
8989
expect(result.fillerWords).toContain('um,');
9090
expect(result.stutters).toContain('n-nervous');
9191
expect(result.repetitions.length).toBeGreaterThanOrEqual(0); // May or may not detect repetitions depending on implementation
9292
});
9393

9494
it('should return empty arrays for normal text', () => {
9595
const result = analyzeSpeechPatterns('This is normal text without speech patterns');
96-
96+
9797
expect(result.fillerWords).toHaveLength(0);
9898
expect(result.repetitions).toHaveLength(0);
9999
expect(result.stutters).toHaveLength(0);

0 commit comments

Comments
 (0)