@lucas-bur/pix 0.4.0 → 0.5.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.
Files changed (2) hide show
  1. package/dist/index.mjs +56 -51
  2. package/package.json +8 -4
package/dist/index.mjs CHANGED
@@ -1,12 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
3
  import { NodeContext, NodeRuntime } from "@effect/platform-node";
4
- import { Clock, Context, Data, Effect, Layer, Option } from "effect";
4
+ import { Clock, Console, Context, Data, Effect, Layer, Option } from "effect";
5
5
  import { Args, CliConfig, Command, Options } from "@effect/cli";
6
6
  import crypto from "node:crypto";
7
7
  import { FileSystem } from "@effect/platform";
8
8
  import { env } from "@huggingface/transformers";
9
- import fg from "fast-glob";
10
9
  import ignore from "ignore";
11
10
  //#region src/domain/ports.ts
12
11
  var ConfigStore = class extends Context.Tag("ConfigStore")() {};
@@ -166,30 +165,22 @@ const indexCommand = Command.make("index", {
166
165
  const result = yield* IndexProject.index().pipe(Effect.either);
167
166
  if (result._tag === "Left") return yield* Effect.fail(result.left);
168
167
  const duration = `${((Date.now() - startTime) / 1e3).toFixed(1)}s`;
169
- if (json) return yield* Effect.sync(() => {
170
- console.log(JSON.stringify({
171
- chunks: result.right.stats.chunks,
172
- files: result.right.stats.files,
173
- duration
174
- }));
175
- });
168
+ if (json) return yield* Console.log(JSON.stringify({
169
+ chunks: result.right.stats.chunks,
170
+ files: result.right.stats.files,
171
+ duration
172
+ }));
176
173
  yield* Effect.logInfo(`Indexed ${result.right.stats.chunks} chunks from ${result.right.stats.files} files in ${duration}.`);
177
- }).pipe(Effect.tapError((error) => Effect.sync(() => {
178
- console.log(formatError(error));
179
- }))));
174
+ }).pipe(Effect.tapError((error) => Console.log(formatError(error)))));
180
175
  //#endregion
181
176
  //#region src/commands/init.ts
182
177
  /** CLI command: pix init [--json] */
183
178
  const initCommand = Command.make("init", { json: Options.boolean("json").pipe(Options.withDefault(false)) }, ({ json }) => Effect.gen(function* () {
184
179
  const result = yield* InitProject.init();
185
- if (json) return yield* Effect.sync(() => {
186
- console.log(JSON.stringify(result, null, 2));
187
- });
180
+ if (json) return yield* Console.log(JSON.stringify(result, null, 2));
188
181
  yield* Effect.logInfo("Created .pix/config.json with default settings.");
189
182
  yield* Effect.logInfo("Reminder: Add `.pix` to your `.gitignore` file to avoid committing the index.");
190
- }).pipe(Effect.tapError((error) => Effect.sync(() => {
191
- console.log(formatError(error));
192
- }))));
183
+ }).pipe(Effect.tapError((error) => Console.log(formatError(error)))));
193
184
  //#endregion
194
185
  //#region src/commands/query.ts
195
186
  const DEFAULT_TOP_K = 5;
@@ -229,7 +220,7 @@ const queryCommand = Command.make("query", {
229
220
  const clamped = clampTopK(topK);
230
221
  if (clamped.clamped) yield* Effect.logDebug(`topK clamped from ${topK} to ${clamped.value}`);
231
222
  const results = yield* QueryProject.queryProject(queryText, clamped.value);
232
- if (json) return yield* Effect.sync(() => {
223
+ if (json) {
233
224
  const output = results.map((r) => ({
234
225
  score: r.score,
235
226
  file: r.file,
@@ -239,19 +230,17 @@ const queryCommand = Command.make("query", {
239
230
  ...ctxLines > 0 && r.contextBefore && { contextBefore: r.contextBefore },
240
231
  ...ctxLines > 0 && r.contextAfter && { contextAfter: r.contextAfter }
241
232
  }));
242
- console.log(JSON.stringify(output, null, 2));
243
- });
233
+ return yield* Console.log(JSON.stringify(output, null, 2));
234
+ }
244
235
  if (results.length === 0) {
245
236
  yield* Effect.logInfo("No results found");
246
237
  return;
247
238
  }
248
- for (const result of results) yield* Effect.sync(() => {
249
- console.log(formatResult(result));
250
- console.log("---");
251
- });
252
- }).pipe(Effect.tapError((error) => Effect.sync(() => {
253
- console.log(formatError(error));
254
- }))));
239
+ for (const result of results) {
240
+ yield* Console.log(formatResult(result));
241
+ yield* Console.log("---");
242
+ }
243
+ }).pipe(Effect.tapError((error) => Console.log(formatError(error)))));
255
244
  //#endregion
256
245
  //#region src/lib/format.ts
257
246
  /** Format byte count as human-readable string (e.g. "1.5 MB") */
@@ -273,15 +262,13 @@ const resetCommand = Command.make("reset", { json: Options.boolean("json").pipe(
273
262
  const start = yield* Clock.currentTimeMillis;
274
263
  const result = yield* ResetIndex.reset();
275
264
  const elapsedMs = (yield* Clock.currentTimeMillis) - start;
276
- if (json) return yield* Effect.sync(() => {
277
- console.log(JSON.stringify({
278
- status: "ok",
279
- deletedChunks: result.deletedChunks,
280
- deletedVectors: result.deletedVectors,
281
- freedBytes: result.freedBytes,
282
- elapsedMs
283
- }));
284
- });
265
+ if (json) return yield* Console.log(JSON.stringify({
266
+ status: "ok",
267
+ deletedChunks: result.deletedChunks,
268
+ deletedVectors: result.deletedVectors,
269
+ freedBytes: result.freedBytes,
270
+ elapsedMs
271
+ }));
285
272
  if (!result.deletedChunks && !result.deletedVectors) {
286
273
  yield* Effect.logInfo("Nothing to reset.");
287
274
  return;
@@ -292,26 +279,20 @@ const resetCommand = Command.make("reset", { json: Options.boolean("json").pipe(
292
279
  yield* Effect.logInfo(`Deleted: ${parts.join(", ")}`);
293
280
  yield* Effect.logInfo(`Freed: ${formatBytes(result.freedBytes)}`);
294
281
  yield* Effect.logInfo(`Time: ${elapsedMs}ms`);
295
- }).pipe(Effect.tapError((error) => Effect.sync(() => {
296
- console.log(formatError(error));
297
- }))));
282
+ }).pipe(Effect.tapError((error) => Console.log(formatError(error)))));
298
283
  //#endregion
299
284
  //#region src/commands/status.ts
300
285
  /** CLI command: pix status [--json] */
301
286
  const statusCommand = Command.make("status", { json: Options.boolean("json").pipe(Options.withDefault(false)) }, ({ json }) => Effect.gen(function* () {
302
287
  const result = yield* GetStatus.getStatus();
303
- if (json) return yield* Effect.sync(() => {
304
- console.log(JSON.stringify(result, null, 2));
305
- });
288
+ if (json) return yield* Console.log(JSON.stringify(result, null, 2));
306
289
  const lastIndexStr = result.lastIndex > 0 ? new Date(result.lastIndex).toISOString() : "never";
307
290
  yield* Effect.logInfo(`Indexed: ${result.chunks} chunks across ${result.files} files`);
308
291
  yield* Effect.logInfo(`Model: ${result.model || "none"}`);
309
292
  yield* Effect.logInfo(`Total lines: ${result.totalLines.toLocaleString()}`);
310
293
  yield* Effect.logInfo(`Index size: ${formatBytes(result.byteSize)}`);
311
294
  yield* Effect.logInfo(`Last indexed: ${lastIndexStr}`);
312
- }).pipe(Effect.tapError((error) => Effect.sync(() => {
313
- console.log(formatError(error));
314
- }))));
295
+ }).pipe(Effect.tapError((error) => Console.log(formatError(error)))));
315
296
  //#endregion
316
297
  //#region src/cli.ts
317
298
  const VERSION = createRequire(import.meta.url)("../package.json").version;
@@ -462,9 +443,16 @@ const make$2 = Effect.gen(function* () {
462
443
  const OnnxEmbedderLive = Layer.effect(Embedder, make$2);
463
444
  //#endregion
464
445
  //#region src/services/scanner.ts
446
+ const ALWAYS_IGNORE = new Set([
447
+ ".pix",
448
+ "node_modules",
449
+ ".git",
450
+ "dist",
451
+ "build",
452
+ ".next"
453
+ ]);
465
454
  const make$1 = Effect.gen(function* () {
466
455
  const fs = yield* FileSystem.FileSystem;
467
- /** Loads all gitignore patterns from .gitignore files in the repo. */
468
456
  const loadGitignoreRules = Effect.gen(function* () {
469
457
  const ig = ignore();
470
458
  const cwd = process.cwd();
@@ -477,13 +465,30 @@ const make$1 = Effect.gen(function* () {
477
465
  }
478
466
  return ig;
479
467
  }).pipe(Effect.catchAll(() => Effect.succeed(ignore())));
468
+ const walk = (dir, extensions) => Effect.gen(function* () {
469
+ const entries = yield* fs.readDirectory(dir).pipe(Effect.catchAll(() => Effect.succeed([])));
470
+ let results = [];
471
+ for (const entry of entries) {
472
+ if (ALWAYS_IGNORE.has(entry)) continue;
473
+ const fullPath = `${dir}/${entry}`;
474
+ const info = yield* fs.stat(fullPath).pipe(Effect.catchAll(() => Effect.succeed(null)));
475
+ if (!info) continue;
476
+ if (info.type === "Directory") {
477
+ const subResults = yield* walk(fullPath, extensions);
478
+ results.push(...subResults);
479
+ } else if (info.type === "File") {
480
+ const dotIndex = entry.lastIndexOf(".");
481
+ if (dotIndex === -1) continue;
482
+ const ext = entry.slice(dotIndex);
483
+ if (extensions.has(ext)) results.push(fullPath);
484
+ }
485
+ }
486
+ return results;
487
+ });
480
488
  const scanFiles = (extensions) => Effect.gen(function* () {
481
489
  const ig = yield* loadGitignoreRules;
482
490
  const cwd = process.cwd();
483
- const pattern = extensions.map((ext) => `**/*${ext}`);
484
- const relativePaths = (yield* Effect.tryPromise(() => fg(pattern, { dot: false })).pipe(Effect.catchAll(() => Effect.succeed([])))).map((p) => {
485
- return p.startsWith(cwd) ? p.slice(cwd.length + 1) : p;
486
- });
491
+ const relativePaths = (yield* walk(cwd, new Set(extensions))).map((p) => p.startsWith(cwd) ? p.slice(cwd.length + 1) : p);
487
492
  return ig.filter(relativePaths).map((p) => `${cwd}/${p}`);
488
493
  });
489
494
  return { scanFiles };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lucas-bur/pix",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Lightweight local semantic project indexer",
5
5
  "keywords": [
6
6
  "cli",
@@ -39,8 +39,13 @@
39
39
  "test": "vp test",
40
40
  "test:coverage": "vp test --coverage",
41
41
  "check": "vp check",
42
- "ci": "vp check && vp test --coverage && vp run build && fallow audit",
43
- "lint:fallow": "fallow --format json",
42
+ "ci": "vp run check && vp run test:coverage && vp run build && vp run lint:effect && vp run lint:fallow",
43
+ "lint:effect": "effect-language-service diagnostics --project tsconfig.json --format pretty --strict",
44
+ "lint:effect:ci": "effect-language-service diagnostics --project tsconfig.json --format github-actions --strict",
45
+ "lint:effect:agent": "effect-language-service diagnostics --project tsconfig.json --format json --strict",
46
+ "lint:fallow": "fallow",
47
+ "lint:fallow:ci": "vpx fallow audit --base main --format badge",
48
+ "lint:fallow:agent": "fallow --format json",
44
49
  "fallow": "fallow",
45
50
  "prepublishOnly": "vp run build",
46
51
  "pix": ""
@@ -51,7 +56,6 @@
51
56
  "@effect/platform-node": "^0.106.0",
52
57
  "@huggingface/transformers": "^4.2.0",
53
58
  "effect": "^3.21.2",
54
- "fast-glob": "^3.3.3",
55
59
  "ignore": "^7.0.5"
56
60
  },
57
61
  "devDependencies": {