@algolia/wizard 0.81.0 → 0.83.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/main.js +495 -51
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2306,7 +2306,7 @@ async function ensureApplication(resumeAppId) {
2306
2306
  // package.json
2307
2307
  var package_default = {
2308
2308
  name: "@algolia/wizard",
2309
- version: "0.81.0",
2309
+ version: "0.83.0",
2310
2310
  description: "Magically implement Algolia functionality in your codebase",
2311
2311
  type: "module",
2312
2312
  engines: {
@@ -3566,30 +3566,379 @@ async function gitIgnoreOutcome(ctx, target) {
3566
3566
  }
3567
3567
 
3568
3568
  // src/lib/tools/searchFiles.ts
3569
+ import { execFile as execFile2 } from "node:child_process";
3569
3570
  import { tool as tool7 } from "ai";
3570
3571
  import z14 from "zod";
3571
- import { readdir as readdir2, readFile as readFile7 } from "node:fs/promises";
3572
- import { join as join8 } from "node:path";
3572
+ import { lstat as lstat3, readdir as readdir2, readFile as readFile7, realpath, stat as stat2 } from "node:fs/promises";
3573
+ import { isAbsolute as isAbsolute2, join as join8, relative as relative4, sep as sep2 } from "node:path";
3574
+ import { performance } from "node:perf_hooks";
3575
+ import { Worker } from "node:worker_threads";
3576
+ var MAX_GIT_OUTPUT_BYTES = 32e6;
3573
3577
  var MAX_QUERY_LENGTH = 1e3;
3578
+ var MAX_MATCH_TEXT_CHARACTERS = 500;
3579
+ var MAX_RESULT_CHARACTERS = 32e3;
3580
+ var REGEX_TIMEOUT_MS = 100;
3581
+ var REGEX_SEARCH_DEADLINE_MS = 2e3;
3582
+ var LINE_NUMBER_OFFSET = 1;
3583
+ var NEWLINE_CHARACTER_COUNT = 1;
3574
3584
  var SKIP_DIRS = /* @__PURE__ */ new Set([
3575
3585
  "node_modules",
3576
3586
  "dist",
3577
3587
  "build",
3588
+ "coverage",
3578
3589
  "vendor",
3579
3590
  "venv",
3580
3591
  "__pycache__",
3581
3592
  "target"
3582
3593
  ]);
3583
- async function walkFiles(dir) {
3594
+ var SKIP_FILES = /* @__PURE__ */ new Set([
3595
+ "bun.lock",
3596
+ "bun.lockb",
3597
+ "Cargo.lock",
3598
+ "composer.lock",
3599
+ "Gemfile.lock",
3600
+ "package-lock.json",
3601
+ "npm-shrinkwrap.json",
3602
+ "Pipfile.lock",
3603
+ "Podfile.lock",
3604
+ "pnpm-lock.yaml",
3605
+ "poetry.lock",
3606
+ "uv.lock",
3607
+ "yarn.lock"
3608
+ ]);
3609
+ var REGEX_WORKER_SOURCE = `
3610
+ const { parentPort, workerData } = require('node:worker_threads')
3611
+
3612
+ function matchingLineSnippet(line, matchIndex) {
3613
+ if (line.length <= workerData.maxMatchTextCharacters) return line
3614
+
3615
+ const contextBefore = Math.floor(workerData.maxMatchTextCharacters / 2)
3616
+ const start = Math.max(0, matchIndex - contextBefore)
3617
+ const end = Math.min(
3618
+ line.length,
3619
+ start + workerData.maxMatchTextCharacters,
3620
+ )
3621
+ const prefix = start > 0 ? '... ' : ''
3622
+ const suffix = end < line.length ? ' ...' : ''
3623
+ return \`\${prefix}\${line.slice(start, end)}\${suffix} (line truncated)\`
3624
+ }
3625
+
3626
+ let expression
3627
+ try {
3628
+ expression = new RegExp(workerData.query)
3629
+ } catch (error) {
3630
+ parentPort.postMessage({
3631
+ type: 'invalid',
3632
+ message: error instanceof Error ? error.message : String(error),
3633
+ })
3634
+ parentPort.close()
3635
+ }
3636
+
3637
+ if (expression !== undefined) {
3638
+ parentPort.postMessage({ type: 'ready' })
3639
+ parentPort.on('message', ({ id, content, limit }) => {
3640
+ const matches = []
3641
+ const lines = content.split('\\n')
3642
+ parentPort.postMessage({ type: 'started', id })
3643
+ for (const [lineIndex, line] of lines.entries()) {
3644
+ const matchIndex = line.search(expression)
3645
+ if (matchIndex >= 0) {
3646
+ matches.push({
3647
+ lineIndex,
3648
+ text: matchingLineSnippet(line, matchIndex),
3649
+ })
3650
+ if (matches.length >= limit) break
3651
+ }
3652
+ }
3653
+ parentPort.postMessage({ type: 'matches', id, matches })
3654
+ })
3655
+ }
3656
+ `;
3657
+ var InvalidRegexError = class extends Error {
3658
+ };
3659
+ var RegexSearchWorker = class _RegexSearchWorker {
3660
+ worker;
3661
+ ready;
3662
+ resolveReady;
3663
+ rejectReady;
3664
+ readySettled = false;
3665
+ stopped = false;
3666
+ nextId = 0;
3667
+ pending;
3668
+ termination;
3669
+ constructor(query) {
3670
+ this.ready = new Promise((resolve6, reject) => {
3671
+ this.resolveReady = resolve6;
3672
+ this.rejectReady = reject;
3673
+ });
3674
+ this.worker = new Worker(REGEX_WORKER_SOURCE, {
3675
+ eval: true,
3676
+ workerData: {
3677
+ query,
3678
+ maxMatchTextCharacters: MAX_MATCH_TEXT_CHARACTERS
3679
+ }
3680
+ });
3681
+ this.worker.on("message", (message) => {
3682
+ this.handleMessage(message);
3683
+ });
3684
+ this.worker.on("error", this.handleWorkerError);
3685
+ this.worker.on("exit", (code) => {
3686
+ if (this.termination === void 0 && !this.stopped) {
3687
+ this.fail(new Error(`Regex worker exited unexpectedly with code ${code}`));
3688
+ }
3689
+ });
3690
+ }
3691
+ static async create(query) {
3692
+ const searchWorker = new _RegexSearchWorker(query);
3693
+ try {
3694
+ await searchWorker.ready;
3695
+ return searchWorker;
3696
+ } catch (error) {
3697
+ await searchWorker.close();
3698
+ throw error;
3699
+ }
3700
+ }
3701
+ match(content, limit, timeoutMs) {
3702
+ if (this.stopped) throw new Error("Regex worker is not available");
3703
+ return new Promise((resolve6, reject) => {
3704
+ const id = ++this.nextId;
3705
+ const pending = { id, timeoutMs, resolve: resolve6, reject };
3706
+ this.pending = pending;
3707
+ try {
3708
+ this.worker.postMessage({ id, content, limit });
3709
+ } catch (error) {
3710
+ this.pending = void 0;
3711
+ reject(error instanceof Error ? error : new Error(String(error)));
3712
+ return;
3713
+ }
3714
+ });
3715
+ }
3716
+ async close() {
3717
+ this.stopped = true;
3718
+ const pending = this.pending;
3719
+ if (pending?.timer !== void 0) clearTimeout(pending.timer);
3720
+ this.termination ??= this.worker.terminate();
3721
+ await this.termination.catch(() => void 0);
3722
+ }
3723
+ handleWorkerError = (error) => {
3724
+ this.fail(error);
3725
+ };
3726
+ handleMessage(message) {
3727
+ if (message.type === "ready") {
3728
+ if (!this.readySettled) {
3729
+ this.readySettled = true;
3730
+ this.resolveReady();
3731
+ }
3732
+ return;
3733
+ }
3734
+ if (message.type === "invalid") {
3735
+ if (!this.readySettled) {
3736
+ this.readySettled = true;
3737
+ this.stopped = true;
3738
+ this.rejectReady(
3739
+ new InvalidRegexError(message.message ?? "Invalid regular expression")
3740
+ );
3741
+ }
3742
+ return;
3743
+ }
3744
+ const pending = this.pending;
3745
+ if (message.type === "started" && message.id !== void 0 && pending !== void 0 && pending.id === message.id) {
3746
+ const startedAt = performance.now();
3747
+ pending.startedAt = startedAt;
3748
+ pending.timer = setTimeout(() => {
3749
+ const current = this.pending;
3750
+ if (current === void 0 || current.id !== message.id) return;
3751
+ this.pending = void 0;
3752
+ this.stopped = true;
3753
+ this.termination = this.worker.terminate();
3754
+ pending.resolve({
3755
+ locations: null,
3756
+ executionMs: performance.now() - startedAt
3757
+ });
3758
+ }, pending.timeoutMs);
3759
+ return;
3760
+ }
3761
+ if (message.type !== "matches" || message.id === void 0 || message.matches === void 0 || pending === void 0 || pending.id !== message.id || pending.startedAt === void 0) {
3762
+ return;
3763
+ }
3764
+ this.pending = void 0;
3765
+ if (pending.timer !== void 0) clearTimeout(pending.timer);
3766
+ pending.resolve({
3767
+ locations: message.matches,
3768
+ executionMs: performance.now() - pending.startedAt
3769
+ });
3770
+ }
3771
+ fail(error) {
3772
+ if (this.stopped) return;
3773
+ this.stopped = true;
3774
+ if (!this.readySettled) {
3775
+ this.readySettled = true;
3776
+ this.rejectReady(error);
3777
+ }
3778
+ const pending = this.pending;
3779
+ this.pending = void 0;
3780
+ if (pending !== void 0) {
3781
+ if (pending.timer !== void 0) clearTimeout(pending.timer);
3782
+ pending.reject(error);
3783
+ }
3784
+ }
3785
+ };
3786
+ function isSearchableGitPath(path, skipGeneratedDirs) {
3787
+ const parts = path.split("/");
3788
+ const fileName = path.slice(path.lastIndexOf("/") + LINE_NUMBER_OFFSET);
3789
+ return !SKIP_FILES.has(fileName) && parts.every(
3790
+ (part) => !part.startsWith(".") && (!skipGeneratedDirs || !SKIP_DIRS.has(part))
3791
+ );
3792
+ }
3793
+ function parseGitFiles(root, output, skipGeneratedDirs) {
3794
+ return output.split("\0").filter(Boolean).filter((path) => isSearchableGitPath(path, skipGeneratedDirs)).map((path) => join8(root, path));
3795
+ }
3796
+ function gitIsUnavailable(error, stderr) {
3797
+ return "code" in error && error.code === "ENOENT" || stderr.includes("not a git repository");
3798
+ }
3799
+ async function listGitFiles(root, pathspec, args, skipGeneratedDirs) {
3800
+ const env = { ...process.env };
3801
+ env.GIT_DIR = void 0;
3802
+ env.GIT_WORK_TREE = void 0;
3803
+ env.GIT_INDEX_FILE = void 0;
3804
+ env.GIT_OBJECT_DIRECTORY = void 0;
3805
+ env.GIT_COMMON_DIR = void 0;
3806
+ return new Promise((resolve6, reject) => {
3807
+ execFile2(
3808
+ "git",
3809
+ ["-C", root, "ls-files", ...args, "-z", "--", pathspec],
3810
+ { env, maxBuffer: MAX_GIT_OUTPUT_BYTES },
3811
+ (error, stdout, stderr) => {
3812
+ if (error === null) {
3813
+ resolve6({
3814
+ available: true,
3815
+ files: parseGitFiles(
3816
+ root,
3817
+ stdout.toString(),
3818
+ skipGeneratedDirs
3819
+ )
3820
+ });
3821
+ return;
3822
+ }
3823
+ if (gitIsUnavailable(error, stderr.toString())) {
3824
+ resolve6({ available: false, files: [] });
3825
+ return;
3826
+ }
3827
+ reject(error);
3828
+ }
3829
+ );
3830
+ });
3831
+ }
3832
+ async function gitVisibleFiles(root, target) {
3833
+ const pathspec = relative4(root, target).split(sep2).join("/") || ".";
3834
+ const [tracked, untracked] = await Promise.all([
3835
+ listGitFiles(root, pathspec, ["--cached"], false),
3836
+ listGitFiles(
3837
+ root,
3838
+ pathspec,
3839
+ ["--others", "--exclude-standard"],
3840
+ true
3841
+ )
3842
+ ]);
3843
+ if (!tracked.available || !untracked.available) {
3844
+ return { available: false, files: [] };
3845
+ }
3846
+ return {
3847
+ available: true,
3848
+ files: [.../* @__PURE__ */ new Set([...tracked.files, ...untracked.files])]
3849
+ };
3850
+ }
3851
+ function isInsideRoot(root, target) {
3852
+ const fromRoot = relative4(root, target);
3853
+ return fromRoot !== ".." && !fromRoot.startsWith(`..${sep2}`) && !isAbsolute2(fromRoot);
3854
+ }
3855
+ async function walkFiles(root, dir) {
3856
+ const [rootPath, dirPath, dirInfo] = await Promise.all([
3857
+ realpath(root),
3858
+ realpath(dir),
3859
+ lstat3(dir)
3860
+ ]);
3861
+ if (dirInfo.isSymbolicLink() || !isInsideRoot(rootPath, dirPath)) {
3862
+ return [];
3863
+ }
3584
3864
  const out = [];
3585
3865
  for (const e of await readdir2(dir, { withFileTypes: true })) {
3586
3866
  if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
3587
3867
  const full = join8(dir, e.name);
3588
- if (e.isDirectory()) out.push(...await walkFiles(full));
3589
- else if (e.isFile()) out.push(full);
3868
+ if (e.isDirectory()) out.push(...await walkFiles(root, full));
3869
+ else if (e.isFile() && !SKIP_FILES.has(e.name)) out.push(full);
3590
3870
  }
3591
3871
  return out;
3592
3872
  }
3873
+ async function expandNestedRepositories(files) {
3874
+ const expanded = [];
3875
+ for (const file of files) {
3876
+ const info = await lstat3(file).catch(() => null);
3877
+ if (info?.isFile() || info?.isSymbolicLink()) {
3878
+ expanded.push(file);
3879
+ continue;
3880
+ }
3881
+ if (!info?.isDirectory()) continue;
3882
+ try {
3883
+ const nested = await gitVisibleFiles(file, file);
3884
+ expanded.push(
3885
+ ...nested.available ? nested.files : await walkFiles(file, file)
3886
+ );
3887
+ } catch (error) {
3888
+ logger.warn(
3889
+ { error, root: file },
3890
+ "searchFiles: nested Git file scan failed, using safe walker"
3891
+ );
3892
+ expanded.push(...await walkFiles(file, file));
3893
+ }
3894
+ }
3895
+ return expanded;
3896
+ }
3897
+ async function gitRootForTarget(root, target) {
3898
+ let repositoryRoot = root;
3899
+ let current = root;
3900
+ const parts = relative4(root, target).split(sep2).filter(Boolean);
3901
+ for (const part of parts) {
3902
+ current = join8(current, part);
3903
+ const info = await lstat3(current).catch(() => null);
3904
+ if (!info?.isDirectory()) return repositoryRoot;
3905
+ const marker = await lstat3(join8(current, ".git")).catch(() => null);
3906
+ if (marker?.isDirectory() || marker?.isFile()) repositoryRoot = current;
3907
+ }
3908
+ return repositoryRoot;
3909
+ }
3910
+ async function safeSearchPath(rootPath, file) {
3911
+ const [info, filePath] = await Promise.all([lstat3(file), realpath(file)]);
3912
+ return info.isFile() && isInsideRoot(rootPath, filePath) ? filePath : null;
3913
+ }
3914
+ async function searchableFiles(root, target) {
3915
+ try {
3916
+ const gitRoot = await gitRootForTarget(root, target);
3917
+ const result = await gitVisibleFiles(gitRoot, target);
3918
+ return result.available ? expandNestedRepositories(result.files) : walkFiles(root, target);
3919
+ } catch (error) {
3920
+ logger.warn(
3921
+ { error, root, target },
3922
+ "searchFiles: Git file scan failed, using safe walker"
3923
+ );
3924
+ return walkFiles(root, target);
3925
+ }
3926
+ }
3927
+ function formatSearchResult(matches, skippedFiles, deadlineReached, workerFailed, finalMarker) {
3928
+ const lines = [...matches];
3929
+ if (skippedFiles > 0) {
3930
+ const fileLabel = skippedFiles === 1 ? "file" : "files";
3931
+ const deadlineMarker = deadlineReached ? ", regex deadline reached" : "";
3932
+ lines.push(
3933
+ `... (partial results: skipped ${skippedFiles} timed-out ${fileLabel}${deadlineMarker})`
3934
+ );
3935
+ }
3936
+ if (workerFailed) {
3937
+ lines.push("... (partial results: regex worker stopped unexpectedly)");
3938
+ }
3939
+ if (finalMarker !== void 0) lines.push(finalMarker);
3940
+ return lines.length > 0 ? lines.join("\n") : "No matches";
3941
+ }
3593
3942
  function searchFilesTool(ctx) {
3594
3943
  return tool7({
3595
3944
  description: "Search file contents for a JavaScript regular expression (RegExp syntax, not grep/PCRE). Returns matching lines as file:line:text.",
@@ -3607,32 +3956,124 @@ function searchFilesTool(ctx) {
3607
3956
  }
3608
3957
  const resolved2 = resolveInRoot(ctx, path);
3609
3958
  if (!resolved2.ok) return resolved2.error;
3610
- let re;
3611
3959
  try {
3612
- re = new RegExp(query);
3960
+ await stat2(resolved2.target);
3613
3961
  } catch (err) {
3614
- return `Invalid regex: ${err.message}`;
3962
+ return `Error searching ${path}: ${err.message}`;
3963
+ }
3964
+ let regexWorker;
3965
+ try {
3966
+ regexWorker = await RegexSearchWorker.create(query);
3967
+ } catch (error) {
3968
+ const message = error instanceof Error ? error.message : String(error);
3969
+ if (error instanceof InvalidRegexError) {
3970
+ return `Invalid regex: ${message}`;
3971
+ }
3972
+ logger.warn({ error }, "searchFiles: regex worker failed to start");
3973
+ return "Refused: regex worker could not start. Retry the search.";
3615
3974
  }
3616
3975
  const matches = [];
3617
- for (const file of await walkFiles(resolved2.target)) {
3618
- let content;
3976
+ let resultCharacters = 0;
3977
+ let skippedFiles = 0;
3978
+ let remainingRegexTime = REGEX_SEARCH_DEADLINE_MS;
3979
+ let deadlineReached = false;
3980
+ let workerFailed = false;
3981
+ try {
3619
3982
  try {
3620
- content = await readFile7(file, "utf8");
3621
- } catch {
3622
- continue;
3623
- }
3624
- const lines = content.split("\n");
3625
- for (let i = 0; i < lines.length; i++) {
3626
- if (re.test(lines[i])) {
3627
- matches.push(`${file}:${i + 1}:${lines[i]}`);
3628
- if (matches.length >= ctx.limits.match) {
3629
- matches.push(`... (truncated at ${ctx.limits.match} matches)`);
3630
- return matches.join("\n");
3983
+ const files = await searchableFiles(ctx.root, resolved2.target);
3984
+ const rootPath = await realpath(ctx.root);
3985
+ for (const [fileIndex, file] of files.entries()) {
3986
+ const moreFilesRemain = fileIndex < files.length - LINE_NUMBER_OFFSET;
3987
+ if (remainingRegexTime <= 0) {
3988
+ deadlineReached = true;
3989
+ break;
3990
+ }
3991
+ let content;
3992
+ try {
3993
+ const filePath = await safeSearchPath(rootPath, file);
3994
+ if (filePath === null) continue;
3995
+ content = await readFile7(filePath, "utf8");
3996
+ } catch {
3997
+ continue;
3998
+ }
3999
+ let result;
4000
+ try {
4001
+ result = await regexWorker.match(
4002
+ content,
4003
+ ctx.limits.match - matches.length,
4004
+ Math.min(REGEX_TIMEOUT_MS, remainingRegexTime)
4005
+ );
4006
+ } catch (error) {
4007
+ logger.warn(
4008
+ { error, file },
4009
+ "searchFiles: regex worker stopped during search"
4010
+ );
4011
+ workerFailed = true;
4012
+ break;
4013
+ }
4014
+ const { executionMs, locations } = result;
4015
+ remainingRegexTime -= executionMs;
4016
+ if (locations === null) {
4017
+ skippedFiles += LINE_NUMBER_OFFSET;
4018
+ if (!moreFilesRemain) break;
4019
+ if (remainingRegexTime <= 0) {
4020
+ deadlineReached = true;
4021
+ break;
4022
+ }
4023
+ await regexWorker.close();
4024
+ try {
4025
+ regexWorker = await RegexSearchWorker.create(query);
4026
+ } catch (error) {
4027
+ logger.warn(
4028
+ { error, file },
4029
+ "searchFiles: regex worker failed to restart"
4030
+ );
4031
+ workerFailed = true;
4032
+ break;
4033
+ }
4034
+ continue;
4035
+ }
4036
+ for (const { lineIndex, text } of locations) {
4037
+ const lineNumber = lineIndex + LINE_NUMBER_OFFSET;
4038
+ const match = `${file}:${lineNumber}:${text}`;
4039
+ const nextResultCharacters = resultCharacters + match.length + NEWLINE_CHARACTER_COUNT;
4040
+ if (nextResultCharacters > MAX_RESULT_CHARACTERS) {
4041
+ matches.push(
4042
+ `... (truncated at ${MAX_RESULT_CHARACTERS} characters)`
4043
+ );
4044
+ return formatSearchResult(
4045
+ matches,
4046
+ skippedFiles,
4047
+ deadlineReached,
4048
+ workerFailed
4049
+ );
4050
+ }
4051
+ matches.push(match);
4052
+ resultCharacters = nextResultCharacters;
4053
+ if (matches.length >= ctx.limits.match) {
4054
+ return formatSearchResult(
4055
+ matches,
4056
+ skippedFiles,
4057
+ deadlineReached,
4058
+ workerFailed,
4059
+ `... (truncated at ${ctx.limits.match} matches)`
4060
+ );
4061
+ }
3631
4062
  }
3632
4063
  }
4064
+ } catch (error) {
4065
+ logger.warn({ error }, "searchFiles: safe file scan failed");
4066
+ return "Refused: Could not list searchable files safely. Fix file access and retry.";
3633
4067
  }
4068
+ } finally {
4069
+ await regexWorker.close();
3634
4070
  }
3635
- return matches.length ? matches.join("\n") : "No matches";
4071
+ return formatSearchResult(
4072
+ matches,
4073
+ skippedFiles,
4074
+ deadlineReached,
4075
+ workerFailed
4076
+ );
3636
4077
  }
3637
4078
  });
3638
4079
  }
@@ -3641,14 +4082,14 @@ function searchFilesTool(ctx) {
3641
4082
  import { tool as tool9, generateText, Output } from "ai";
3642
4083
  import { createAnthropic } from "@ai-sdk/anthropic";
3643
4084
  import z16 from "zod";
3644
- import { relative as relative5 } from "node:path";
4085
+ import { relative as relative6 } from "node:path";
3645
4086
 
3646
4087
  // src/lib/tools/reviewScript.ts
3647
4088
  import { tool as tool8 } from "ai";
3648
4089
  import z15 from "zod";
3649
4090
  import { createHash as createHash2 } from "node:crypto";
3650
- import { readFile as readFile8, stat as stat2 } from "node:fs/promises";
3651
- import { basename as basename2, relative as relative4, resolve as resolve3 } from "node:path";
4091
+ import { readFile as readFile8, stat as stat3 } from "node:fs/promises";
4092
+ import { basename as basename2, relative as relative5, resolve as resolve3 } from "node:path";
3652
4093
 
3653
4094
  // src/lib/tools/utils/prompt.ts
3654
4095
  var chain = Promise.resolve();
@@ -3747,7 +4188,7 @@ function commandDiffersFromReviewedScript(ctx, command, cwd) {
3747
4188
  if (approval.command === command) return true;
3748
4189
  const scriptReferences = [
3749
4190
  basename2(approval.filePath),
3750
- relative4(ctx.root, approval.filePath),
4191
+ relative5(ctx.root, approval.filePath),
3751
4192
  ".algolia-wizard"
3752
4193
  ];
3753
4194
  return scriptReferences.some(
@@ -3796,19 +4237,19 @@ function reviewScriptTool(ctx) {
3796
4237
  if (resolved2.ok === false) return resolved2.error;
3797
4238
  const resolvedCwd = resolveInRoot(ctx, cwd);
3798
4239
  if (resolvedCwd.ok === false) return resolvedCwd.error;
3799
- const displayCwd = relative4(ctx.root, resolvedCwd.target) || ".";
3800
- const displayFilePath = relative4(ctx.root, resolved2.target);
4240
+ const displayCwd = relative5(ctx.root, resolvedCwd.target) || ".";
4241
+ const displayFilePath = relative5(ctx.root, resolved2.target);
3801
4242
  const ingestionSummary = usesGeneratedRecords ? `This will push ${entityName} records from generated sample data to Algolia. You can replace the sample dataset with your real data source in ${displayFilePath}.` : `This will push ${entityName} records from your selected source to Algolia.`;
3802
4243
  return serializePrompt(async () => {
3803
4244
  revokeCommandApprovals(ctx, command, resolvedCwd.target);
3804
- const isFile = await stat2(resolved2.target).then(
4245
+ const isFile = await stat3(resolved2.target).then(
3805
4246
  (s) => s.isFile(),
3806
4247
  () => false
3807
4248
  );
3808
4249
  if (!isFile) {
3809
4250
  return `Refused: ${filePath} is not a file. Write the script first, then review the path you wrote.`;
3810
4251
  }
3811
- const relativeScriptPath = relative4(resolvedCwd.target, resolved2.target);
4252
+ const relativeScriptPath = relative5(resolvedCwd.target, resolved2.target);
3812
4253
  if (!commandReferencesScript(
3813
4254
  command,
3814
4255
  resolved2.target,
@@ -3968,7 +4409,7 @@ function storeApproval(root) {
3968
4409
  return async (req) => {
3969
4410
  const store = useWizard.getState();
3970
4411
  if (store.isCommandApproved(req.command, req.cwd)) return "approve";
3971
- const rel = relative5(root, req.cwd);
4412
+ const rel = relative6(root, req.cwd);
3972
4413
  const answer = await store.requestUserInput({
3973
4414
  prompt: "Run this command?",
3974
4415
  promptType: "commandApproval",
@@ -4194,8 +4635,8 @@ function defaultCreateModel() {
4194
4635
  }
4195
4636
  function approvedCommandHistory(approvedCommands) {
4196
4637
  return Array.from(approvedCommands).map((entry) => {
4197
- const sep3 = entry.indexOf("\0");
4198
- return { cwd: entry.slice(0, sep3), command: entry.slice(sep3 + 1) };
4638
+ const sep4 = entry.indexOf("\0");
4639
+ return { cwd: entry.slice(0, sep4), command: entry.slice(sep4 + 1) };
4199
4640
  });
4200
4641
  }
4201
4642
  async function classifyCommandSafety(createModel, command, cwd, explanation, approvedHistory) {
@@ -4312,14 +4753,14 @@ async function runReviewedCommand(ctx, command, cwd) {
4312
4753
  const approval = getReviewedCommandApproval(ctx, command, cwd);
4313
4754
  if (!approval) return void 0;
4314
4755
  if (!await reviewedCommandApprovalIsCurrent(approval)) {
4315
- return `Refused: ${relative5(ctx.root, approval.filePath)} changed after review. Call reviewScript again before running it.`;
4756
+ return `Refused: ${relative6(ctx.root, approval.filePath)} changed after review. Call reviewScript again before running it.`;
4316
4757
  }
4317
4758
  const [env, secrets] = await Promise.all([
4318
4759
  resolveShellEnv(ctx, command),
4319
4760
  resolveSecretShellEnv(ctx, command)
4320
4761
  ]);
4321
4762
  if (!await reviewedCommandApprovalIsCurrent(approval)) {
4322
- return `Refused: ${relative5(ctx.root, approval.filePath)} changed after review. Call reviewScript again before running it.`;
4763
+ return `Refused: ${relative6(ctx.root, approval.filePath)} changed after review. Call reviewScript again before running it.`;
4323
4764
  }
4324
4765
  const result = await runAndRecord(
4325
4766
  ctx,
@@ -4923,10 +5364,13 @@ var MODE_CONFIG = {
4923
5364
  ingestion: {
4924
5365
  instructions: [
4925
5366
  "Analyze the codebase to find the data entities (models) that should be ingested into Algolia.",
5367
+ "Assume this is a new Algolia customer project. Existing Algolia dependencies, credentials, indices, and integration code are not required.",
5368
+ "This workflow creates the Algolia integration, so its absence is expected and is not a failure.",
5369
+ "Infer entity candidates from domain models, database schemas, data-access code, API handlers, and representative application data.",
4926
5370
  "For each entity, return its name, the file path(s) where it is defined, and its indexable attribute keys (the fields a user would search or filter on).",
4927
5371
  "Inspect the source of each entity to extract real field names for attributes \u2014 do not guess or leave attributes empty.",
4928
5372
  "Prefer domain models (e.g. Document, Product, User) over framework or infrastructure types.",
4929
- "Use as few tools as possible, but do not guess. If you cannot find any entities, return an empty array.",
5373
+ "Use as few tools as possible, but do not guess. If no domain entities exist, report success with an empty array.",
4930
5374
  "Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
4931
5375
  "When done, call reportStatus"
4932
5376
  ],
@@ -5389,16 +5833,16 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
5389
5833
  // src/actions/implement.ts
5390
5834
  import z28 from "zod";
5391
5835
  import { access, mkdir as mkdir5, readFile as readFile9 } from "node:fs/promises";
5392
- import { dirname as dirname5, isAbsolute as isAbsolute3, join as join11, relative as relative6, resolve as resolve5, sep as sep2 } from "node:path";
5836
+ import { dirname as dirname5, isAbsolute as isAbsolute4, join as join11, relative as relative7, resolve as resolve5, sep as sep3 } from "node:path";
5393
5837
 
5394
5838
  // src/lib/git.ts
5395
- import { execFile as execFile2 } from "node:child_process";
5396
- import { copyFile, mkdir as mkdir4, stat as stat3 } from "node:fs/promises";
5397
- import { basename as basename3, dirname as dirname3, isAbsolute as isAbsolute2, join as join9, resolve as resolve4 } from "node:path";
5839
+ import { execFile as execFile3 } from "node:child_process";
5840
+ import { copyFile, mkdir as mkdir4, stat as stat4 } from "node:fs/promises";
5841
+ import { basename as basename3, dirname as dirname3, isAbsolute as isAbsolute3, join as join9, resolve as resolve4 } from "node:path";
5398
5842
  var MAX_BUFFER = 32 * 1024 * 1024;
5399
5843
  function git(args) {
5400
5844
  return new Promise((resolve6, reject) => {
5401
- execFile2("git", args, { maxBuffer: MAX_BUFFER }, (err, stdout, stderr) => {
5845
+ execFile3("git", args, { maxBuffer: MAX_BUFFER }, (err, stdout, stderr) => {
5402
5846
  if (err)
5403
5847
  return reject(
5404
5848
  new Error(
@@ -5423,9 +5867,9 @@ async function copyUploadIntoProject(repoRoot, ingestDir, sourcePath) {
5423
5867
  if (!trimmed) {
5424
5868
  return { ok: false, reason: "no file path was provided" };
5425
5869
  }
5426
- const source = isAbsolute2(trimmed) ? trimmed : resolve4(repoRoot, trimmed);
5870
+ const source = isAbsolute3(trimmed) ? trimmed : resolve4(repoRoot, trimmed);
5427
5871
  try {
5428
- if (!(await stat3(source)).isFile()) {
5872
+ if (!(await stat4(source)).isFile()) {
5429
5873
  return { ok: false, reason: `"${sourcePath}" is not a file` };
5430
5874
  }
5431
5875
  } catch {
@@ -5819,7 +6263,7 @@ function parseIngestRecordCount(output) {
5819
6263
  return Number.isFinite(count) ? count : void 0;
5820
6264
  }
5821
6265
  function relativeIngestCwd(execution, repoRoot) {
5822
- const cwd = relative6(repoRoot, execution.cwd);
6266
+ const cwd = relative7(repoRoot, execution.cwd);
5823
6267
  return cwd && cwd !== "." ? cwd : ".";
5824
6268
  }
5825
6269
  function reportedCommandWithCwdNote(execution, repoRoot) {
@@ -5936,8 +6380,8 @@ async function frontendHasPackageJson(repoRoot, searchLocation) {
5936
6380
  let directory = repoRoot;
5937
6381
  if (searchLocation) {
5938
6382
  const candidate = dirname5(resolve5(repoRoot, searchLocation));
5939
- const candidateRelative = relative6(repoRoot, candidate);
5940
- const outsideRoot = candidateRelative === ".." || candidateRelative.startsWith(`..${sep2}`) || isAbsolute3(candidateRelative);
6383
+ const candidateRelative = relative7(repoRoot, candidate);
6384
+ const outsideRoot = candidateRelative === ".." || candidateRelative.startsWith(`..${sep3}`) || isAbsolute4(candidateRelative);
5941
6385
  if (!outsideRoot) directory = candidate;
5942
6386
  }
5943
6387
  for (; ; ) {
@@ -6169,7 +6613,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
6169
6613
  if (agentRuns > 0) ctx.recordStepExecution();
6170
6614
  agentRuns += 1;
6171
6615
  const writtenFiles = [...new Set(useWizard.getState().writtenFiles)].map(
6172
- (file) => relative6(repoRoot, file)
6616
+ (file) => relative7(repoRoot, file)
6173
6617
  );
6174
6618
  return runAgent({
6175
6619
  operation: "search-validation",
@@ -6309,7 +6753,7 @@ ${detail}` : ""}`
6309
6753
  const trackComponentGenerated = (mounted_successfully, attempts) => {
6310
6754
  const searchFilesChanged = [
6311
6755
  ...new Set(useWizard.getState().writtenFiles)
6312
- ].map((file) => relative6(repoRoot, file));
6756
+ ].map((file) => relative7(repoRoot, file));
6313
6757
  track("AI Wizard Frontend Component Generated", {
6314
6758
  file_paths: searchFilesChanged,
6315
6759
  framework,
@@ -6344,7 +6788,7 @@ ${detail}` : ""}`
6344
6788
  buildCheckPlan ??= await resolveBuildCheckPlan(ctx, repoRoot, input);
6345
6789
  const searchFilesChanged = [
6346
6790
  ...new Set(useWizard.getState().writtenFiles)
6347
- ].map((file) => relative6(repoRoot, file));
6791
+ ].map((file) => relative7(repoRoot, file));
6348
6792
  const validation = await runValidationUseCase(
6349
6793
  validationRunInstructions(buildCheckPlan, searchFilesChanged, attempt)
6350
6794
  );
@@ -8547,7 +8991,7 @@ async function resetProjectState() {
8547
8991
  // package.json with { type: 'json' }
8548
8992
  var package_default2 = {
8549
8993
  name: "@algolia/wizard",
8550
- version: "0.81.0",
8994
+ version: "0.83.0",
8551
8995
  description: "Magically implement Algolia functionality in your codebase",
8552
8996
  type: "module",
8553
8997
  engines: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algolia/wizard",
3
- "version": "0.81.0",
3
+ "version": "0.83.0",
4
4
  "description": "Magically implement Algolia functionality in your codebase",
5
5
  "type": "module",
6
6
  "engines": {