@algolia/wizard 0.83.0 → 0.84.0-rc.202.645
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/dist/main.js
CHANGED
|
@@ -884,20 +884,28 @@ function useScrollWindow({
|
|
|
884
884
|
revealIndex
|
|
885
885
|
};
|
|
886
886
|
}
|
|
887
|
-
function ScrollView({ scroll, children }) {
|
|
888
|
-
return /* @__PURE__ */ jsxs5(
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
scroll.
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
887
|
+
function ScrollView({ scroll, grow = true, children }) {
|
|
888
|
+
return /* @__PURE__ */ jsxs5(
|
|
889
|
+
Box6,
|
|
890
|
+
{
|
|
891
|
+
ref: scroll.viewportRef,
|
|
892
|
+
flexDirection: "column",
|
|
893
|
+
flexGrow: grow ? 1 : 0,
|
|
894
|
+
children: [
|
|
895
|
+
scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
|
|
896
|
+
"\u2191 ",
|
|
897
|
+
scroll.hiddenAbove,
|
|
898
|
+
" more"
|
|
899
|
+
] }),
|
|
900
|
+
children,
|
|
901
|
+
scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
|
|
902
|
+
"\u2193 ",
|
|
903
|
+
scroll.hiddenBelow,
|
|
904
|
+
" more"
|
|
905
|
+
] })
|
|
906
|
+
]
|
|
907
|
+
}
|
|
908
|
+
);
|
|
901
909
|
}
|
|
902
910
|
|
|
903
911
|
// src/ui/Review.tsx
|
|
@@ -1082,6 +1090,7 @@ function CommandApproval({
|
|
|
1082
1090
|
if (scroll.measured) setWidth(scroll.width);
|
|
1083
1091
|
}, [scroll.measured, scroll.width]);
|
|
1084
1092
|
const scrollable = scroll.maxOffset > 0;
|
|
1093
|
+
const grow = width === 0 || scrollable;
|
|
1085
1094
|
useInput3(
|
|
1086
1095
|
(input) => {
|
|
1087
1096
|
if (input === "k") scroll.scrollBy(-1);
|
|
@@ -1104,10 +1113,10 @@ function CommandApproval({
|
|
|
1104
1113
|
borderBottom: false,
|
|
1105
1114
|
borderRight: false,
|
|
1106
1115
|
width: "100%",
|
|
1107
|
-
flexGrow: 1,
|
|
1116
|
+
flexGrow: grow ? 1 : 0,
|
|
1108
1117
|
flexShrink: 1,
|
|
1109
1118
|
minHeight: 3,
|
|
1110
|
-
children: /* @__PURE__ */ jsx6(ScrollView, { scroll, children: visible.map((line, i) => /* @__PURE__ */ jsxs7(Text8, { wrap: "truncate", children: [
|
|
1119
|
+
children: /* @__PURE__ */ jsx6(ScrollView, { scroll, grow, children: visible.map((line, i) => /* @__PURE__ */ jsxs7(Text8, { wrap: "truncate", children: [
|
|
1111
1120
|
line.prefix ? /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: line.prefix }) : null,
|
|
1112
1121
|
/* @__PURE__ */ jsx6(Text8, { color: line.color, children: line.text })
|
|
1113
1122
|
] }, `cmd-${scroll.offset + i}`)) })
|
|
@@ -1483,7 +1492,7 @@ function PromptInput() {
|
|
|
1483
1492
|
}
|
|
1484
1493
|
|
|
1485
1494
|
// src/workflows/default.ts
|
|
1486
|
-
import { z as
|
|
1495
|
+
import { z as z30 } from "zod";
|
|
1487
1496
|
|
|
1488
1497
|
// src/core/orchestrator.ts
|
|
1489
1498
|
import "zod";
|
|
@@ -2306,7 +2315,7 @@ async function ensureApplication(resumeAppId) {
|
|
|
2306
2315
|
// package.json
|
|
2307
2316
|
var package_default = {
|
|
2308
2317
|
name: "@algolia/wizard",
|
|
2309
|
-
version: "0.
|
|
2318
|
+
version: "0.84.0-rc.202.645",
|
|
2310
2319
|
description: "Magically implement Algolia functionality in your codebase",
|
|
2311
2320
|
type: "module",
|
|
2312
2321
|
engines: {
|
|
@@ -2797,6 +2806,22 @@ async function listIndices() {
|
|
|
2797
2806
|
|
|
2798
2807
|
// src/actions/selectIndex.ts
|
|
2799
2808
|
var CREATE_NEW_INDEX = "Create a new index\u2026";
|
|
2809
|
+
var MAX_INDEX_NAME_LENGTH = 255;
|
|
2810
|
+
function indexNameError(name) {
|
|
2811
|
+
if (!name) return "Index name cannot be empty.";
|
|
2812
|
+
if (name.length > MAX_INDEX_NAME_LENGTH)
|
|
2813
|
+
return `Index name cannot be longer than ${MAX_INDEX_NAME_LENGTH} characters.`;
|
|
2814
|
+
if (name.startsWith(".")) return "Index name cannot start with '.'.";
|
|
2815
|
+
if (/[/~,\\`&|;$*]/.test(name))
|
|
2816
|
+
return "Index name cannot contain any of these characters: / ~ , \\ ` & | ; $ *";
|
|
2817
|
+
if (/[\p{Cc}]/u.test(name))
|
|
2818
|
+
return "Index name cannot contain control characters.";
|
|
2819
|
+
if (/[\u{10000}-\u{10FFFF}]/u.test(name))
|
|
2820
|
+
return "Index name cannot contain characters outside the Basic Multilingual Plane.";
|
|
2821
|
+
if (/\.(txt|bin|idx)$/.test(name) && !name.endsWith(".indice.bin"))
|
|
2822
|
+
return "Index name cannot end with .txt, .bin, or .idx.";
|
|
2823
|
+
return void 0;
|
|
2824
|
+
}
|
|
2800
2825
|
var selectIndexStep = async (ctx) => {
|
|
2801
2826
|
await requireApplication(ctx.appId);
|
|
2802
2827
|
const indices = await listIndices();
|
|
@@ -2824,6 +2849,7 @@ var selectIndexStep = async (ctx) => {
|
|
|
2824
2849
|
throw new Error("selectIndex received an unexpected non-text result");
|
|
2825
2850
|
}
|
|
2826
2851
|
let candidate;
|
|
2852
|
+
let isNewName = true;
|
|
2827
2853
|
if (!hasIndices) {
|
|
2828
2854
|
candidate = selection.trim();
|
|
2829
2855
|
} else if (selection === CREATE_NEW_INDEX) {
|
|
@@ -2838,9 +2864,11 @@ var selectIndexStep = async (ctx) => {
|
|
|
2838
2864
|
candidate = name.trim();
|
|
2839
2865
|
} else {
|
|
2840
2866
|
candidate = selection;
|
|
2867
|
+
isNewName = false;
|
|
2841
2868
|
}
|
|
2842
|
-
|
|
2843
|
-
|
|
2869
|
+
const nameError = isNewName ? indexNameError(candidate) : void 0;
|
|
2870
|
+
if (nameError) {
|
|
2871
|
+
error = nameError;
|
|
2844
2872
|
continue;
|
|
2845
2873
|
}
|
|
2846
2874
|
chosen = candidate;
|
|
@@ -2856,6 +2884,7 @@ import {
|
|
|
2856
2884
|
Output as Output3,
|
|
2857
2885
|
APICallError,
|
|
2858
2886
|
NoOutputGeneratedError,
|
|
2887
|
+
RetryError,
|
|
2859
2888
|
stepCountIs
|
|
2860
2889
|
} from "ai";
|
|
2861
2890
|
import { createAnthropic as createAnthropic3 } from "@ai-sdk/anthropic";
|
|
@@ -2869,6 +2898,58 @@ import { tool } from "ai";
|
|
|
2869
2898
|
import z6 from "zod";
|
|
2870
2899
|
import { readdir } from "node:fs/promises";
|
|
2871
2900
|
|
|
2901
|
+
// src/lib/tools/context.ts
|
|
2902
|
+
var DEFAULT_TOOL_LIMITS = {
|
|
2903
|
+
list: 10,
|
|
2904
|
+
search: 20,
|
|
2905
|
+
read: 20,
|
|
2906
|
+
match: 100,
|
|
2907
|
+
shell: 50
|
|
2908
|
+
};
|
|
2909
|
+
var EMPTY_TOOL_COUNTS = {
|
|
2910
|
+
list: 0,
|
|
2911
|
+
search: 0,
|
|
2912
|
+
read: 0,
|
|
2913
|
+
shell: 0
|
|
2914
|
+
};
|
|
2915
|
+
var DEFAULT_SHELL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
2916
|
+
async function refuseByDefault() {
|
|
2917
|
+
return "reject";
|
|
2918
|
+
}
|
|
2919
|
+
async function emptyShellEnv() {
|
|
2920
|
+
await Promise.resolve();
|
|
2921
|
+
return {};
|
|
2922
|
+
}
|
|
2923
|
+
function createShellContext(overrides = {}) {
|
|
2924
|
+
return {
|
|
2925
|
+
env: emptyShellEnv,
|
|
2926
|
+
secretEnv: emptyShellEnv,
|
|
2927
|
+
timeoutMs: DEFAULT_SHELL_TIMEOUT_MS,
|
|
2928
|
+
executions: [],
|
|
2929
|
+
approve: refuseByDefault,
|
|
2930
|
+
...overrides
|
|
2931
|
+
};
|
|
2932
|
+
}
|
|
2933
|
+
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd(), shell2 = createShellContext()) {
|
|
2934
|
+
return {
|
|
2935
|
+
root: cwd,
|
|
2936
|
+
cwd,
|
|
2937
|
+
limits: { ...limits },
|
|
2938
|
+
counts: { ...EMPTY_TOOL_COUNTS },
|
|
2939
|
+
shell: shell2,
|
|
2940
|
+
reviewed: []
|
|
2941
|
+
};
|
|
2942
|
+
}
|
|
2943
|
+
function readLimitRefusal(kind, limit) {
|
|
2944
|
+
return `Refused: ${kind} limit (${limit}) reached. This limit is not a failure, so do not report fail because of it. Finish the task now with the information you already have. If the task changes files, write or edit every file that the task still needs. Then report success, and name any gap in the summary.`;
|
|
2945
|
+
}
|
|
2946
|
+
function readLimitReached(ctx) {
|
|
2947
|
+
return ctx.counts.read > ctx.limits.read || ctx.counts.search > ctx.limits.search || ctx.counts.list > ctx.limits.list;
|
|
2948
|
+
}
|
|
2949
|
+
function resetToolCounts(ctx) {
|
|
2950
|
+
Object.assign(ctx.counts, EMPTY_TOOL_COUNTS);
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2872
2953
|
// src/lib/tools/path.ts
|
|
2873
2954
|
import { constants } from "node:fs";
|
|
2874
2955
|
import { lstat, mkdir as mkdir3, open } from "node:fs/promises";
|
|
@@ -2950,7 +3031,7 @@ function listFilesTool(ctx) {
|
|
|
2950
3031
|
execute: async ({ path = "." }) => {
|
|
2951
3032
|
logger.info({ path }, "called listFiles tool");
|
|
2952
3033
|
if (++ctx.counts.list > ctx.limits.list) {
|
|
2953
|
-
return
|
|
3034
|
+
return readLimitRefusal("list", ctx.limits.list);
|
|
2954
3035
|
}
|
|
2955
3036
|
const resolved2 = resolveInRoot(ctx, path);
|
|
2956
3037
|
if (!resolved2.ok) return resolved2.error;
|
|
@@ -3026,6 +3107,9 @@ function isSecretEnvFile(filePath) {
|
|
|
3026
3107
|
const name = basename(filePath);
|
|
3027
3108
|
return !/\.(example|sample|template)$/.test(name);
|
|
3028
3109
|
}
|
|
3110
|
+
function secretEnvWriteRefusal(filePath) {
|
|
3111
|
+
return `Refused: ${filePath} holds secrets and cannot be written directly. If a writeCredentials tool is available, use it to set Algolia credentials. If not, tell the user to set the value manually \u2014 do not reproduce the secret value in your response.`;
|
|
3112
|
+
}
|
|
3029
3113
|
|
|
3030
3114
|
// src/lib/tools/utils/truncateText.ts
|
|
3031
3115
|
var HEAD_CHARS = 4e3;
|
|
@@ -3095,7 +3179,7 @@ function readFileTool(ctx) {
|
|
|
3095
3179
|
}),
|
|
3096
3180
|
execute: async ({ filePath, readLines }) => {
|
|
3097
3181
|
if (++ctx.counts.read > ctx.limits.read) {
|
|
3098
|
-
return
|
|
3182
|
+
return readLimitRefusal("read", ctx.limits.read);
|
|
3099
3183
|
}
|
|
3100
3184
|
logger.info({ filePath, readLines }, "called readFile tool");
|
|
3101
3185
|
const resolved2 = resolveInRoot(ctx, filePath);
|
|
@@ -3116,7 +3200,7 @@ import { tool as tool5 } from "ai";
|
|
|
3116
3200
|
import z10 from "zod";
|
|
3117
3201
|
function writeFileTool(ctx) {
|
|
3118
3202
|
return tool5({
|
|
3119
|
-
description: "Write content to a file at the given path, overwriting it. Writes to secret env files (.env, .env.local, etc.) are refused by this tool \u2014 call it anyway and the tool will tell you how to proceed.",
|
|
3203
|
+
description: "Write content to a file at the given path, overwriting it. Use it to create a file or to replace all of its content; to change part of an existing file, use editFile. Writes to secret env files (.env, .env.local, etc.) are refused by this tool \u2014 call it anyway and the tool will tell you how to proceed.",
|
|
3120
3204
|
inputSchema: z10.object({
|
|
3121
3205
|
filePath: z10.string().describe("Path to the file to write"),
|
|
3122
3206
|
content: z10.string().describe("Content to write to the file")
|
|
@@ -3126,7 +3210,7 @@ function writeFileTool(ctx) {
|
|
|
3126
3210
|
const resolved2 = resolveInRoot(ctx, filePath);
|
|
3127
3211
|
if (resolved2.ok === false) return resolved2.error;
|
|
3128
3212
|
if (isSecretEnvFile(resolved2.target)) {
|
|
3129
|
-
return
|
|
3213
|
+
return secretEnvWriteRefusal(filePath);
|
|
3130
3214
|
}
|
|
3131
3215
|
try {
|
|
3132
3216
|
const writeError = await safeWriteFile(ctx, resolved2.target, content);
|
|
@@ -3140,22 +3224,107 @@ function writeFileTool(ctx) {
|
|
|
3140
3224
|
});
|
|
3141
3225
|
}
|
|
3142
3226
|
|
|
3143
|
-
// src/lib/tools/
|
|
3227
|
+
// src/lib/tools/editFile.ts
|
|
3144
3228
|
import { tool as tool6 } from "ai";
|
|
3145
|
-
import
|
|
3146
|
-
import { readFile as
|
|
3229
|
+
import z11 from "zod";
|
|
3230
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
3231
|
+
var SNIPPET_CONTEXT_LINES = 3;
|
|
3232
|
+
var SNIPPET_MAX_LINES = 40;
|
|
3233
|
+
function countOccurrences(content, search2) {
|
|
3234
|
+
let count = 0;
|
|
3235
|
+
let index = content.indexOf(search2);
|
|
3236
|
+
while (index !== -1) {
|
|
3237
|
+
count += 1;
|
|
3238
|
+
index = content.indexOf(search2, index + search2.length);
|
|
3239
|
+
}
|
|
3240
|
+
return count;
|
|
3241
|
+
}
|
|
3242
|
+
function replaceFirst(content, search2, replacement) {
|
|
3243
|
+
const index = content.indexOf(search2);
|
|
3244
|
+
return content.slice(0, index) + replacement + content.slice(index + search2.length);
|
|
3245
|
+
}
|
|
3246
|
+
function editedSnippet(updated, editIndex, newString) {
|
|
3247
|
+
const lines = updated.split("\n");
|
|
3248
|
+
const editStart = updated.slice(0, editIndex).split("\n").length - 1;
|
|
3249
|
+
const editEnd = updated.slice(0, editIndex + newString.length).split("\n").length - (newString.endsWith("\n") ? 1 : 0);
|
|
3250
|
+
const from = Math.max(0, editStart - SNIPPET_CONTEXT_LINES);
|
|
3251
|
+
const to = Math.min(
|
|
3252
|
+
lines.length,
|
|
3253
|
+
editEnd + SNIPPET_CONTEXT_LINES,
|
|
3254
|
+
from + SNIPPET_MAX_LINES
|
|
3255
|
+
);
|
|
3256
|
+
return `readLines [${from}, ${to}] now contains:
|
|
3257
|
+
${truncateText(lines.slice(from, to).join("\n"))}`;
|
|
3258
|
+
}
|
|
3259
|
+
function editFileTool(ctx) {
|
|
3260
|
+
return tool6({
|
|
3261
|
+
description: "Edit an existing file by replacing an exact string with a new string. Prefer this over writeFile for any change to an existing file: send only the changed part, not the whole file. oldString must match the file content exactly, including whitespace and indentation, and must be unique unless replaceAll is true. Include enough surrounding lines to make it unique. Read the file first so the match is exact. A single edit returns the edited lines with some context, so do not read the file again to check the edit.",
|
|
3262
|
+
inputSchema: z11.object({
|
|
3263
|
+
filePath: z11.string().describe("Path to the file to edit"),
|
|
3264
|
+
oldString: z11.string().describe("Exact text to replace"),
|
|
3265
|
+
newString: z11.string().describe("Text to put in place of oldString"),
|
|
3266
|
+
replaceAll: z11.boolean().optional().describe("Replace every occurrence of oldString (default false)")
|
|
3267
|
+
}),
|
|
3268
|
+
execute: async ({ filePath, oldString, newString, replaceAll }) => {
|
|
3269
|
+
logger.info({ filePath, replaceAll }, "called editFile tool");
|
|
3270
|
+
const resolved2 = resolveInRoot(ctx, filePath);
|
|
3271
|
+
if (resolved2.ok === false) return resolved2.error;
|
|
3272
|
+
if (isSecretEnvFile(resolved2.target)) {
|
|
3273
|
+
return secretEnvWriteRefusal(filePath);
|
|
3274
|
+
}
|
|
3275
|
+
if (oldString === "") {
|
|
3276
|
+
return "Error: oldString is empty. To create a file, use writeFile.";
|
|
3277
|
+
}
|
|
3278
|
+
if (oldString === newString) {
|
|
3279
|
+
return "Error: oldString and newString are identical. No change made.";
|
|
3280
|
+
}
|
|
3281
|
+
try {
|
|
3282
|
+
const unsafe = await validateSafeWriteTarget(ctx, resolved2.target);
|
|
3283
|
+
if (unsafe) return unsafe;
|
|
3284
|
+
let content;
|
|
3285
|
+
try {
|
|
3286
|
+
content = await readFile5(resolved2.target, "utf8");
|
|
3287
|
+
} catch (err) {
|
|
3288
|
+
if (err.code === "ENOENT") {
|
|
3289
|
+
return `Error: ${filePath} does not exist. To create it, use writeFile.`;
|
|
3290
|
+
}
|
|
3291
|
+
throw err;
|
|
3292
|
+
}
|
|
3293
|
+
const matches = countOccurrences(content, oldString);
|
|
3294
|
+
if (matches === 0) {
|
|
3295
|
+
return `Error: oldString not found in ${filePath}. Read the file again and copy the exact text, including whitespace.`;
|
|
3296
|
+
}
|
|
3297
|
+
if (matches > 1 && !replaceAll) {
|
|
3298
|
+
return `Error: oldString matches ${matches} places in ${filePath}. Add surrounding lines to make it unique, or set replaceAll to true.`;
|
|
3299
|
+
}
|
|
3300
|
+
const updated = replaceAll ? content.split(oldString).join(newString) : replaceFirst(content, oldString, newString);
|
|
3301
|
+
const writeError = await safeWriteFile(ctx, resolved2.target, updated);
|
|
3302
|
+
if (writeError) return writeError;
|
|
3303
|
+
useWizard.getState().recordWrittenFile(resolved2.target);
|
|
3304
|
+
return matches > 1 ? `Edited ${filePath} (${matches} replacements)` : `Edited ${filePath}. ${editedSnippet(updated, content.indexOf(oldString), newString)}`;
|
|
3305
|
+
} catch (err) {
|
|
3306
|
+
return `Error editing ${filePath}: ${err.message}`;
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
});
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
3313
|
+
import { tool as tool7 } from "ai";
|
|
3314
|
+
import z14 from "zod";
|
|
3315
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
3147
3316
|
import { relative as relative3 } from "node:path";
|
|
3148
3317
|
|
|
3149
3318
|
// src/lib/algoliaApiKey.ts
|
|
3150
|
-
import { z as
|
|
3319
|
+
import { z as z13 } from "zod";
|
|
3151
3320
|
|
|
3152
3321
|
// src/lib/keychain.ts
|
|
3153
3322
|
import { createHash } from "node:crypto";
|
|
3154
3323
|
import { deletePassword, getPassword, setPassword } from "cross-keychain";
|
|
3155
|
-
import { z as
|
|
3324
|
+
import { z as z12 } from "zod";
|
|
3156
3325
|
var SERVICE = "algolia-wizard";
|
|
3157
3326
|
var account = (userId) => `api-keys-${createHash("sha256").update(userId).digest("hex")}`;
|
|
3158
|
-
var storedKeysSchema =
|
|
3327
|
+
var storedKeysSchema = z12.record(z12.string(), z12.string());
|
|
3159
3328
|
function entryId(kind, index, appId) {
|
|
3160
3329
|
return `${kind}:${appId}:${index}`;
|
|
3161
3330
|
}
|
|
@@ -3249,9 +3418,9 @@ var WRITE_ACLS = [
|
|
|
3249
3418
|
"editSettings",
|
|
3250
3419
|
"listIndexes"
|
|
3251
3420
|
];
|
|
3252
|
-
var createdKeySchema =
|
|
3253
|
-
key:
|
|
3254
|
-
value:
|
|
3421
|
+
var createdKeySchema = z13.object({
|
|
3422
|
+
key: z13.string().min(1).optional(),
|
|
3423
|
+
value: z13.string().min(1).optional()
|
|
3255
3424
|
}).transform((o) => o.key ?? o.value);
|
|
3256
3425
|
async function createKey(index, acls, description) {
|
|
3257
3426
|
logger.info({ index, acls }, "creating an API key");
|
|
@@ -3347,7 +3516,7 @@ async function resolveSearchOnlyKey(index, appId) {
|
|
|
3347
3516
|
|
|
3348
3517
|
// src/lib/gitignore.ts
|
|
3349
3518
|
import { execFile } from "node:child_process";
|
|
3350
|
-
import { lstat as lstat2, readFile as
|
|
3519
|
+
import { lstat as lstat2, readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
|
|
3351
3520
|
import { join as join7, relative as relative2 } from "node:path";
|
|
3352
3521
|
var GIT_ENV_OVERRIDES = [
|
|
3353
3522
|
"GIT_DIR",
|
|
@@ -3410,7 +3579,7 @@ async function ensureGitIgnored(root, target) {
|
|
|
3410
3579
|
);
|
|
3411
3580
|
return "unknown";
|
|
3412
3581
|
}
|
|
3413
|
-
const existing = link ? await
|
|
3582
|
+
const existing = link ? await readFile6(gitIgnore, "utf8") : "";
|
|
3414
3583
|
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
3415
3584
|
await writeFile3(gitIgnore, `${existing}${prefix}${pattern}
|
|
3416
3585
|
`, "utf8");
|
|
@@ -3457,10 +3626,10 @@ function upsertEnv(content, name, value) {
|
|
|
3457
3626
|
);
|
|
3458
3627
|
}
|
|
3459
3628
|
function writeCredentialsTool(ctx) {
|
|
3460
|
-
return
|
|
3629
|
+
return tool7({
|
|
3461
3630
|
description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) and the target index name (${INDEX_NAME_VAR}) into the given env file. The credentials come from the selected Algolia application, with a write key scoped to the target index; you only pass the path to the env file. ${INDEX_NAME_VAR} is always set to this run's target index, replacing any value already there. An ${APP_ID_VAR} or ${API_KEY_VAR} the file already gives a value is left untouched; a missing or blank one is filled in when it can be paired with the selected application. The env file is added to .gitignore automatically; do not edit .gitignore yourself. If the script or app that reads these credentials lives in a subdirectory (e.g. a package in a monorepo), an env file at the repo root is the wrong default \u2014 a script only loads env vars from its own directory (or one it's explicitly configured to read), so check every directory from the script's own up to the repo root, not just those two: its own directory, each ancestor in between (a shared workspace-level directory above the immediate package is common), and the root. Use whichever of those already holds real credentials; only fall back to the repo root when none of them do. Never invent a brand-new file in one of those directories when a real one already exists in another \u2014 that leaves the real one stale and the new one wrong. Listing just the script's own directory and the very top-level root is not enough to find a workspace-level file in between; check the intermediate ones too. If instructions describe a location that doesn't match the project you actually find (e.g. a path outside the repo, or a convention the project doesn't follow), don't stop and ask before doing anything \u2014 call this tool on the real, in-repo file the script actually reads (that's always the safe default), then note the mismatch afterward. Ending your turn with only a question and no call to this tool leaves the project unconfigured.`,
|
|
3462
|
-
inputSchema:
|
|
3463
|
-
filePath:
|
|
3631
|
+
inputSchema: z14.object({
|
|
3632
|
+
filePath: z14.string().describe(
|
|
3464
3633
|
'Path to the env file to write credentials into, relative to the repo root (e.g. ".env", or "packages/api/.env" when the consuming script lives in that package)'
|
|
3465
3634
|
)
|
|
3466
3635
|
}),
|
|
@@ -3478,7 +3647,7 @@ function writeCredentialsTool(ctx) {
|
|
|
3478
3647
|
const targetError = await validateSafeWriteTarget(ctx, resolved2.target);
|
|
3479
3648
|
if (targetError) return targetError;
|
|
3480
3649
|
try {
|
|
3481
|
-
existing = await
|
|
3650
|
+
existing = await readFile7(resolved2.target, "utf8");
|
|
3482
3651
|
} catch (err) {
|
|
3483
3652
|
if (err.code !== "ENOENT") throw err;
|
|
3484
3653
|
}
|
|
@@ -3567,9 +3736,9 @@ async function gitIgnoreOutcome(ctx, target) {
|
|
|
3567
3736
|
|
|
3568
3737
|
// src/lib/tools/searchFiles.ts
|
|
3569
3738
|
import { execFile as execFile2 } from "node:child_process";
|
|
3570
|
-
import { tool as
|
|
3571
|
-
import
|
|
3572
|
-
import { lstat as lstat3, readdir as readdir2, readFile as
|
|
3739
|
+
import { tool as tool8 } from "ai";
|
|
3740
|
+
import z15 from "zod";
|
|
3741
|
+
import { lstat as lstat3, readdir as readdir2, readFile as readFile8, realpath, stat as stat2 } from "node:fs/promises";
|
|
3573
3742
|
import { isAbsolute as isAbsolute2, join as join8, relative as relative4, sep as sep2 } from "node:path";
|
|
3574
3743
|
import { performance } from "node:perf_hooks";
|
|
3575
3744
|
import { Worker } from "node:worker_threads";
|
|
@@ -3940,16 +4109,16 @@ function formatSearchResult(matches, skippedFiles, deadlineReached, workerFailed
|
|
|
3940
4109
|
return lines.length > 0 ? lines.join("\n") : "No matches";
|
|
3941
4110
|
}
|
|
3942
4111
|
function searchFilesTool(ctx) {
|
|
3943
|
-
return
|
|
4112
|
+
return tool8({
|
|
3944
4113
|
description: "Search file contents for a JavaScript regular expression (RegExp syntax, not grep/PCRE). Returns matching lines as file:line:text.",
|
|
3945
|
-
inputSchema:
|
|
3946
|
-
query:
|
|
3947
|
-
path:
|
|
4114
|
+
inputSchema: z15.object({
|
|
4115
|
+
query: z15.string().describe("JavaScript RegExp pattern to search for"),
|
|
4116
|
+
path: z15.string().optional().describe("Directory to search in (default: cwd)")
|
|
3948
4117
|
}),
|
|
3949
4118
|
execute: async ({ query, path = "." }) => {
|
|
3950
4119
|
logger.info({ query, path }, "called searchFiles tool");
|
|
3951
4120
|
if (++ctx.counts.search > ctx.limits.search) {
|
|
3952
|
-
return
|
|
4121
|
+
return readLimitRefusal("search", ctx.limits.search);
|
|
3953
4122
|
}
|
|
3954
4123
|
if (query.length > MAX_QUERY_LENGTH) {
|
|
3955
4124
|
return `Refused: query exceeds ${MAX_QUERY_LENGTH} characters. Use a shorter pattern.`;
|
|
@@ -3992,7 +4161,7 @@ function searchFilesTool(ctx) {
|
|
|
3992
4161
|
try {
|
|
3993
4162
|
const filePath = await safeSearchPath(rootPath, file);
|
|
3994
4163
|
if (filePath === null) continue;
|
|
3995
|
-
content = await
|
|
4164
|
+
content = await readFile8(filePath, "utf8");
|
|
3996
4165
|
} catch {
|
|
3997
4166
|
continue;
|
|
3998
4167
|
}
|
|
@@ -4079,16 +4248,16 @@ function searchFilesTool(ctx) {
|
|
|
4079
4248
|
}
|
|
4080
4249
|
|
|
4081
4250
|
// src/lib/tools/runShell.ts
|
|
4082
|
-
import { tool as
|
|
4251
|
+
import { tool as tool10, generateText, Output } from "ai";
|
|
4083
4252
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
4084
|
-
import
|
|
4253
|
+
import z17 from "zod";
|
|
4085
4254
|
import { relative as relative6 } from "node:path";
|
|
4086
4255
|
|
|
4087
4256
|
// src/lib/tools/reviewScript.ts
|
|
4088
|
-
import { tool as
|
|
4089
|
-
import
|
|
4257
|
+
import { tool as tool9 } from "ai";
|
|
4258
|
+
import z16 from "zod";
|
|
4090
4259
|
import { createHash as createHash2 } from "node:crypto";
|
|
4091
|
-
import { readFile as
|
|
4260
|
+
import { readFile as readFile9, stat as stat3 } from "node:fs/promises";
|
|
4092
4261
|
import { basename as basename2, relative as relative5, resolve as resolve3 } from "node:path";
|
|
4093
4262
|
|
|
4094
4263
|
// src/lib/tools/utils/prompt.ts
|
|
@@ -4103,7 +4272,7 @@ function serializePrompt(work) {
|
|
|
4103
4272
|
// src/lib/tools/reviewScript.ts
|
|
4104
4273
|
var reviewedCommandApprovals = /* @__PURE__ */ new WeakMap();
|
|
4105
4274
|
async function fileDigest(filePath) {
|
|
4106
|
-
const content = await
|
|
4275
|
+
const content = await readFile9(filePath);
|
|
4107
4276
|
return createHash2("sha256").update(content).digest("hex");
|
|
4108
4277
|
}
|
|
4109
4278
|
function grantReviewedCommandApproval(ctx, approval) {
|
|
@@ -4207,18 +4376,18 @@ async function reviewedCommandApprovalIsCurrent(approval) {
|
|
|
4207
4376
|
return currentDigest === approval.digest;
|
|
4208
4377
|
}
|
|
4209
4378
|
function reviewScriptTool(ctx) {
|
|
4210
|
-
return
|
|
4379
|
+
return tool9({
|
|
4211
4380
|
description: "Call this as soon as the ingestion script is finished and before you run it. It summarizes the records that will be written, then asks the user to review the script and approve the exact command that will run it, so a matching runShell call needs no second approval.",
|
|
4212
|
-
inputSchema:
|
|
4213
|
-
filePath:
|
|
4381
|
+
inputSchema: z16.object({
|
|
4382
|
+
filePath: z16.string().describe(
|
|
4214
4383
|
"Path to the script to review, relative to the project root."
|
|
4215
4384
|
),
|
|
4216
|
-
command:
|
|
4217
|
-
cwd:
|
|
4385
|
+
command: z16.string().min(1).describe("The exact command that runShell will use to run the script."),
|
|
4386
|
+
cwd: z16.string().describe(
|
|
4218
4387
|
"The exact working directory that runShell will use, relative to the current tool working directory."
|
|
4219
4388
|
),
|
|
4220
|
-
entityName:
|
|
4221
|
-
usesGeneratedRecords:
|
|
4389
|
+
entityName: z16.string().trim().min(1).max(100).regex(/^[^\r\n]+$/).describe("The exact entity name selected by the user."),
|
|
4390
|
+
usesGeneratedRecords: z16.boolean().describe(
|
|
4222
4391
|
"Whether the script reads generated sample records instead of the selected real data source."
|
|
4223
4392
|
)
|
|
4224
4393
|
}),
|
|
@@ -4309,54 +4478,6 @@ function redactSecrets(output, secrets) {
|
|
|
4309
4478
|
|
|
4310
4479
|
// src/lib/tools/utils/runShell.ts
|
|
4311
4480
|
import { spawn as spawn2 } from "node:child_process";
|
|
4312
|
-
|
|
4313
|
-
// src/lib/tools/context.ts
|
|
4314
|
-
var DEFAULT_TOOL_LIMITS = {
|
|
4315
|
-
list: 10,
|
|
4316
|
-
search: 20,
|
|
4317
|
-
read: 20,
|
|
4318
|
-
match: 100,
|
|
4319
|
-
shell: 50
|
|
4320
|
-
};
|
|
4321
|
-
var EMPTY_TOOL_COUNTS = {
|
|
4322
|
-
list: 0,
|
|
4323
|
-
search: 0,
|
|
4324
|
-
read: 0,
|
|
4325
|
-
shell: 0
|
|
4326
|
-
};
|
|
4327
|
-
var DEFAULT_SHELL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
4328
|
-
async function refuseByDefault() {
|
|
4329
|
-
return "reject";
|
|
4330
|
-
}
|
|
4331
|
-
async function emptyShellEnv() {
|
|
4332
|
-
await Promise.resolve();
|
|
4333
|
-
return {};
|
|
4334
|
-
}
|
|
4335
|
-
function createShellContext(overrides = {}) {
|
|
4336
|
-
return {
|
|
4337
|
-
env: emptyShellEnv,
|
|
4338
|
-
secretEnv: emptyShellEnv,
|
|
4339
|
-
timeoutMs: DEFAULT_SHELL_TIMEOUT_MS,
|
|
4340
|
-
executions: [],
|
|
4341
|
-
approve: refuseByDefault,
|
|
4342
|
-
...overrides
|
|
4343
|
-
};
|
|
4344
|
-
}
|
|
4345
|
-
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd(), shell2 = createShellContext()) {
|
|
4346
|
-
return {
|
|
4347
|
-
root: cwd,
|
|
4348
|
-
cwd,
|
|
4349
|
-
limits: { ...limits },
|
|
4350
|
-
counts: { ...EMPTY_TOOL_COUNTS },
|
|
4351
|
-
shell: shell2,
|
|
4352
|
-
reviewed: []
|
|
4353
|
-
};
|
|
4354
|
-
}
|
|
4355
|
-
function resetToolCounts(ctx) {
|
|
4356
|
-
Object.assign(ctx.counts, EMPTY_TOOL_COUNTS);
|
|
4357
|
-
}
|
|
4358
|
-
|
|
4359
|
-
// src/lib/tools/utils/runShell.ts
|
|
4360
4481
|
var SIGKILL_DELAY_MS = 5e3;
|
|
4361
4482
|
function runShell(command, opts) {
|
|
4362
4483
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
|
|
@@ -4618,9 +4739,9 @@ function fastPathSafety(command) {
|
|
|
4618
4739
|
if (results.every((r) => r === true)) return true;
|
|
4619
4740
|
return void 0;
|
|
4620
4741
|
}
|
|
4621
|
-
var commandSafetySchema =
|
|
4622
|
-
safe:
|
|
4623
|
-
reason:
|
|
4742
|
+
var commandSafetySchema = z17.object({
|
|
4743
|
+
safe: z17.boolean(),
|
|
4744
|
+
reason: z17.string().describe("One short sentence explaining the verdict.")
|
|
4624
4745
|
});
|
|
4625
4746
|
function defaultCreateModel() {
|
|
4626
4747
|
const token = getAuthToken();
|
|
@@ -4661,17 +4782,17 @@ async function classifyCommandSafety(createModel, command, cwd, explanation, app
|
|
|
4661
4782
|
prompt: [
|
|
4662
4783
|
"A coding agent wants to run this shell command in a user's project without asking for approval first. The command may be in any programming language or ecosystem.",
|
|
4663
4784
|
"Judge it SAFE only if it cannot modify, delete, move, rename, or install anything the project cares about, cannot push/publish/deploy/commit anything, and cannot make a network request that changes remote state.",
|
|
4664
|
-
"Never judge a command SAFE if it runs, imports, requires, or otherwise
|
|
4785
|
+
"Never judge a command SAFE if it runs, imports, requires, or otherwise executes an ingestion script or any code under `.algolia-wizard/`. This rule includes shell wrappers, inline code, variable indirection, and dry-run or check descriptions, and no earlier approval overrides it. Type-checking, parsing, or compiling such a file without running it (e.g. `tsc --noEmit <file>`, `node --check <file>`) does not execute it: judge that check unsafe the first time, and SAFE when the precedent rule at the end applies.",
|
|
4665
4786
|
"Three broad categories are safe, and most commands you will see fall into one of them:",
|
|
4666
4787
|
"(1) Reporting or listing existing state, with no side effect \u2014 e.g. `git status`, `npm ls`, `npm outdated`, `pip show`, `pip freeze`, `cargo tree`, `docker ps`, `docker images`, `kubectl get pods`, `terraform state list`. This includes a read-only GET-style query to a remote registry or API that only fetches public metadata and changes nothing remote (e.g. `npm view <package>`, `pip index versions <package>`, `curl` with no -X/--request other than GET and no -d/--data) \u2014 safe because nothing changes, not because it stays local.",
|
|
4667
4788
|
'(2) A "preview" or "check" mode that reports what a change WOULD do, or reports a problem, without making the change. This is a general pattern, not a fixed list \u2014 ANY command that takes a dry-run/check/plan/validate/diff/list-only flag is safe when that flag is present, regardless of whether the same flag would normally appear on a "safe" kind of command: `terraform plan`, `terraform validate`, `terraform fmt -check`, `black --check`, `prettier --check`, `isort --check`, `gofmt -l` (list-only), `stylelint` with no `--fix`, and equally `git push --dry-run`, `npm publish --dry-run`, `kubectl apply --dry-run=client` \u2014 these last three are otherwise-unsafe operations (push, publish, cluster changes) that the dry-run flag turns into a report. The corresponding command WITHOUT that flag (e.g. `terraform apply`, `black .`, `gofmt -w`, `git push`) is a different, unsafe command \u2014 the flag is what makes the difference, for any tool, not just the ones named here. This cuts both ways, so do not assume a bare invocation is the safe one: `cargo fmt`, `black`, `prettier`, `isort`, `rustfmt`, and `terraform fmt` all REWRITE FILES by default and need an explicit check/diff/dry-run flag to become safe, while `gofmt`, `eslint`, `stylelint`, `rubocop`, and `ruff check` default to a safe check-only mode and need an explicit fix/write flag to become unsafe \u2014 the same-looking bare command is safe for one group and unsafe for the other, so judge each by what its own flags actually say, not by resemblance to a tool you already judged.',
|
|
4668
4789
|
"(3) Running the project's own tests, type checker, linter, or build/compile step (e.g. `npm run build`, `yarn build`, `vite build`, `webpack`, `tsc`, `cargo build`, `go build`, `mvn compile`), as long as it is not passed an autofix/write/update flag (e.g. --fix, -u, --write, rubocop -a) \u2014 this holds even though the point of a build step is to write compiled output to a build/dist/target directory inside the project (e.g. a target/, build/, dist/, or __pycache__ directory): writing that output does not make the command unsafe. It stops being safe the moment the command chain goes past building \u2014 a step that also deploys, publishes, uploads, or pushes the build (e.g. `next build && vercel deploy`, `npm run build && npm publish`) is unsafe for that later segment even though the build segment itself is fine; judge each chained segment on its own, same as elsewhere in this list.",
|
|
4669
4790
|
"Deleting or removing anything is unsafe, even something in a cache or build directory (e.g. `rm -rf __pycache__`, `cargo clean`, `git clean`), and even if the command otherwise fits one of the three categories above. Installing, uninstalling, or upgrading a dependency, changing a database schema, or writing to a path outside the project is also unsafe. A command chained with &&, ;, a pipe, or a line break (LF or CRLF) is only safe if every command segment is safe on its own. Treat each line as a separate command segment, even when the first line is safe.",
|
|
4670
|
-
"One specific, narrow exception to all of the above: when the command literally invokes one of these runner programs BY NAME as the leading command \u2014 npx, bunx, pnpm dlx, yarn dlx, pipx run, uvx \u2014 it is unsafe regardless of what it then runs, even something that looks like a harmless test/lint/typecheck command (e.g. `npx tsc --noEmit`, `uvx ruff check .`), because that runner can fetch and execute a different, unreviewed version of a package from a registry each time. This exception is about that specific syntax, not a general doubt about whether a tool is installed: a plain `ruff check .`, `pytest`, or any other bare command name is NOT this exception merely because you cannot verify from the string alone that it is installed \u2014 judge those the same as any other already-installed project tool, per the categories above. If you are unsure, judge it unsafe.",
|
|
4791
|
+
"One specific, narrow exception to all of the above: when the command literally invokes one of these runner programs BY NAME as the leading command \u2014 npx, bunx, pnpm dlx, yarn dlx, pipx run, uvx \u2014 it is unsafe regardless of what it then runs, even something that looks like a harmless test/lint/typecheck command (e.g. `npx tsc --noEmit`, `uvx ruff check .`), because that runner can fetch and execute a different, unreviewed version of a package from a registry each time. This exception is about that specific syntax, not a general doubt about whether a tool is installed: a plain `ruff check .`, `pytest`, or any other bare command name is NOT this exception merely because you cannot verify from the string alone that it is installed \u2014 judge those the same as any other already-installed project tool, per the categories above. If you are unsure, judge it unsafe. This runner rule yields to the precedent rule at the end: a later invocation of an approved runner and package (e.g. `npx tsc ...`) is SAFE only when it has the same action and side-effect profile as the approved one.",
|
|
4671
4792
|
...approvedHistory.length > 0 ? [
|
|
4672
4793
|
"The user has already explicitly approved these exact commands earlier in this session (working directory in parentheses, then the command):",
|
|
4673
4794
|
approvedHistory.map((h) => `- (${h.cwd}) ${h.command}`).join("\n"),
|
|
4674
|
-
"
|
|
4795
|
+
"Precedent rule: if the new command performs the same action and side-effect profile as one of these \u2014 differing only in a trivial way, such as a different file path, flag, or argument value that does not change what kind of action it is \u2014 judge it SAFE on that precedent. A different package name is NOT trivial for a dependency install or a runner (npx, bunx, pnpm dlx, yarn dlx, pipx run, uvx): approving `npm install algoliasearch` does not approve `npm install <other-pkg>`, because installing runs lifecycle scripts. This overrides categories (1)-(3), the dependency install and upgrade rule, the runner rule, and the `.algolia-wizard/` check rule above. Precedent never makes SAFE a command that executes code under `.algolia-wizard/`. Do not stretch this to a command that merely shares a binary name, or superficially resembles one on the list while doing something riskier or of a different kind (e.g. an approved `rm build/tmp.log` does not license a new `rm -rf src/`, and an approved `git push origin feature-x` does not license `git push --force`)."
|
|
4675
4796
|
] : [],
|
|
4676
4797
|
`Command: ${command}`,
|
|
4677
4798
|
`Working directory: ${cwd}`,
|
|
@@ -4716,7 +4837,9 @@ async function resolveSecretShellEnv(ctx, command) {
|
|
|
4716
4837
|
});
|
|
4717
4838
|
}
|
|
4718
4839
|
async function runAndRecord(ctx, command, environment) {
|
|
4719
|
-
useWizard.getState().pushNotice({
|
|
4840
|
+
useWizard.getState().pushNotice({
|
|
4841
|
+
messages: [`Running: ${command.replace(/\s*\n\s*/g, " ").trim()}`]
|
|
4842
|
+
});
|
|
4720
4843
|
const run2 = await (ctx.shell.run ?? runShell)(command, {
|
|
4721
4844
|
cwd: environment.cwd,
|
|
4722
4845
|
env: environment.values,
|
|
@@ -4789,16 +4912,16 @@ async function runUnreviewedCommand(ctx, command, cwd) {
|
|
|
4789
4912
|
return runAndRecord(ctx, command, { cwd, values: env, secrets });
|
|
4790
4913
|
}
|
|
4791
4914
|
function runShellTool(ctx, createModel = defaultCreateModel) {
|
|
4792
|
-
return
|
|
4915
|
+
return tool10({
|
|
4793
4916
|
description: "Run a shell command in the project. Use this for anything the project needs done in its own ecosystem: installing dependencies, running a script you wrote, running the project's lint/typecheck/test commands. Do not use this to find or read files \u2014 use listFiles, searchFiles, and readFile instead of shell file-listing, reading, or search commands (ls/find/tree/cat/head/tail/grep/rg and similar). This tool refuses all of those outright: the dedicated tools enforce the project-root boundary and secret redaction that a raw shell read would bypass, and they won't count against this tool's command budget. The user approves any command that could change the project before it runs, so write a clear `explanation`. A command judged read-only (inspection, or running tests/typecheck/lint without an autofix flag, in any language) runs immediately without approval. If the user rejects a command, do not retry it \u2014 propose a different approach.",
|
|
4794
|
-
inputSchema:
|
|
4795
|
-
command:
|
|
4917
|
+
inputSchema: z17.object({
|
|
4918
|
+
command: z17.string().describe(
|
|
4796
4919
|
"The command to run, exactly as it would be typed in a shell. Pipes, && and redirects are allowed."
|
|
4797
4920
|
),
|
|
4798
|
-
cwd:
|
|
4921
|
+
cwd: z17.string().optional().describe(
|
|
4799
4922
|
"Directory to run in, relative to the project root. Defaults to the project root."
|
|
4800
4923
|
),
|
|
4801
|
-
explanation:
|
|
4924
|
+
explanation: z17.string().describe(
|
|
4802
4925
|
"One short line telling the user what this command does and why, including any side effect (e.g. writes records to Algolia). This is what they approve against."
|
|
4803
4926
|
)
|
|
4804
4927
|
}),
|
|
@@ -4854,10 +4977,10 @@ function runShellTool(ctx, createModel = defaultCreateModel) {
|
|
|
4854
4977
|
}
|
|
4855
4978
|
|
|
4856
4979
|
// src/lib/tools/generateRecord.ts
|
|
4857
|
-
import { tool as
|
|
4980
|
+
import { tool as tool11, generateText as generateText2, Output as Output2, NoObjectGeneratedError } from "ai";
|
|
4858
4981
|
import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
|
|
4859
4982
|
import { nanoid as nanoid2 } from "nanoid";
|
|
4860
|
-
import
|
|
4983
|
+
import z18 from "zod";
|
|
4861
4984
|
var DATA_DIR = ".algolia-wizard/data";
|
|
4862
4985
|
var MAX_RECORDS = 100;
|
|
4863
4986
|
var BATCH_SIZE = 10;
|
|
@@ -4874,20 +4997,20 @@ function defaultCreateModel2() {
|
|
|
4874
4997
|
});
|
|
4875
4998
|
}
|
|
4876
4999
|
function generateRecordTool(ctx, createModel = defaultCreateModel2) {
|
|
4877
|
-
return
|
|
5000
|
+
return tool11({
|
|
4878
5001
|
description: "Generate realistic sample records for an entity and write them to a JSON file. Provide the entity name and its attributes; this tool asks a model to invent varied, realistic values, each with a unique objectID, and returns the file path to read them from at runtime. Do not invent the record values or objectIDs yourself, and do not inline the returned records into the script \u2014 call this tool and read the file it writes.",
|
|
4879
|
-
inputSchema:
|
|
4880
|
-
entityName:
|
|
4881
|
-
attributes:
|
|
4882
|
-
count:
|
|
4883
|
-
hint:
|
|
5002
|
+
inputSchema: z18.object({
|
|
5003
|
+
entityName: z18.string().describe("Name of the entity to generate records for."),
|
|
5004
|
+
attributes: z18.array(z18.string()).describe("Attribute names each record must contain."),
|
|
5005
|
+
count: z18.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
|
|
5006
|
+
hint: z18.string().optional().describe("Optional context to steer realistic values.")
|
|
4884
5007
|
}),
|
|
4885
5008
|
execute: async ({ entityName, attributes, count, hint }) => {
|
|
4886
5009
|
logger.info({ entityName, count }, "called generateRecord tool");
|
|
4887
5010
|
try {
|
|
4888
5011
|
const anthropic = createModel();
|
|
4889
|
-
const value =
|
|
4890
|
-
const recordSchema2 =
|
|
5012
|
+
const value = z18.union([z18.string(), z18.number(), z18.boolean(), z18.null()]);
|
|
5013
|
+
const recordSchema2 = z18.object(
|
|
4891
5014
|
Object.fromEntries(attributes.map((attr) => [attr, value]))
|
|
4892
5015
|
);
|
|
4893
5016
|
const generateBatch = async (batchCount) => {
|
|
@@ -4909,8 +5032,8 @@ function generateRecordTool(ctx, createModel = defaultCreateModel2) {
|
|
|
4909
5032
|
}
|
|
4910
5033
|
},
|
|
4911
5034
|
output: Output2.object({
|
|
4912
|
-
schema:
|
|
4913
|
-
records:
|
|
5035
|
+
schema: z18.object({
|
|
5036
|
+
records: z18.array(recordSchema2).length(batchCount)
|
|
4914
5037
|
})
|
|
4915
5038
|
}),
|
|
4916
5039
|
prompt: [
|
|
@@ -4981,13 +5104,13 @@ function generateRecordTool(ctx, createModel = defaultCreateModel2) {
|
|
|
4981
5104
|
}
|
|
4982
5105
|
|
|
4983
5106
|
// src/lib/tools/notifyUser.ts
|
|
4984
|
-
import { tool as
|
|
4985
|
-
import
|
|
5107
|
+
import { tool as tool12 } from "ai";
|
|
5108
|
+
import z19 from "zod";
|
|
4986
5109
|
function notifyUserTool() {
|
|
4987
|
-
return
|
|
5110
|
+
return tool12({
|
|
4988
5111
|
description: `Give the user a brief, high-level update on what you are currently doing or about to do next. This is for the big picture (e.g. "Reading through your data models", "Writing the search UI") \u2014 not granular detail like individual tool calls, which are already logged separately. Call it when you start a new phase of work or your focus shifts, just not on every step, enough to keep the user engaged. Don't say things like "starting", just describe what you are doing. Don't mention tool calls themselves, just general direction of the work. Only the latest update is shown on screen, in one line, so keep it under 140 characters.`,
|
|
4989
|
-
inputSchema:
|
|
4990
|
-
message:
|
|
5112
|
+
inputSchema: z19.object({
|
|
5113
|
+
message: z19.string().max(140).describe(
|
|
4991
5114
|
"Short, plain-language description of what you are doing now. Under 140 characters \u2014 only the latest update is shown, on one line."
|
|
4992
5115
|
)
|
|
4993
5116
|
}),
|
|
@@ -5025,6 +5148,7 @@ function createTools(ctx, { output, tools }) {
|
|
|
5025
5148
|
reportStatus: withLogging("reportStatus", reportStatusTool(output)),
|
|
5026
5149
|
readFile: withLogging("readFile", readFileTool(ctx)),
|
|
5027
5150
|
writeFile: withLogging("writeFile", writeFileTool(ctx)),
|
|
5151
|
+
editFile: withLogging("editFile", editFileTool(ctx)),
|
|
5028
5152
|
writeCredentials: withLogging(
|
|
5029
5153
|
"writeCredentials",
|
|
5030
5154
|
writeCredentialsTool(ctx)
|
|
@@ -5052,9 +5176,19 @@ var FS_READ_TOOLS = [
|
|
|
5052
5176
|
var MISSING_REPORT_STATUS_ERROR_MESSAGE = "Agent finished without calling reportStatus";
|
|
5053
5177
|
var MISSING_REPORT_USER_MESSAGE = "This step ran into a problem finishing. Run the wizard again to retry it.";
|
|
5054
5178
|
var REPORT_STATUS_RETRIES = 2;
|
|
5179
|
+
var STEP_LIMIT_INSTRUCTION = "When reportStatus is the only tool available, you reached the step limit. This limit is not a failure, so do not report fail because of it: report status=success with the work done so far, and name the remaining work in the summary.";
|
|
5180
|
+
var RECENT_STEPS_LOGGED = 3;
|
|
5181
|
+
var LOGGED_CHARS = 300;
|
|
5055
5182
|
var ATTEMPT_NUMBER_OFFSET = 1;
|
|
5056
5183
|
var INITIAL_ATTEMPT = 0;
|
|
5057
5184
|
var PROVIDER_ERROR_USER_MESSAGE = "The AI service had trouble responding. Run the wizard again to retry this step.";
|
|
5185
|
+
var SESSION_TIMEOUT_USER_MESSAGE = "The wizard session timed out after a period of inactivity. Run the wizard again to retry this step.";
|
|
5186
|
+
var PROXY_AUTH_REJECTED_HEADER2 = "x-wizard-proxy-auth";
|
|
5187
|
+
var PROXY_REJECTION_BODY = "You shall not pass!";
|
|
5188
|
+
function isProxyInactivityRejection(err) {
|
|
5189
|
+
const cause = RetryError.isInstance(err) ? err.lastError : err;
|
|
5190
|
+
return APICallError.isInstance(cause) && cause.statusCode === 401 && cause.responseBody === PROXY_REJECTION_BODY && cause.responseHeaders?.[PROXY_AUTH_REJECTED_HEADER2] === void 0;
|
|
5191
|
+
}
|
|
5058
5192
|
var MissingReportError = class extends Error {
|
|
5059
5193
|
atStepLimit;
|
|
5060
5194
|
constructor(atStepLimit) {
|
|
@@ -5062,6 +5196,13 @@ var MissingReportError = class extends Error {
|
|
|
5062
5196
|
this.atStepLimit = atStepLimit;
|
|
5063
5197
|
}
|
|
5064
5198
|
};
|
|
5199
|
+
var AgentReportedFailureError = class extends Error {
|
|
5200
|
+
reason;
|
|
5201
|
+
constructor(reason) {
|
|
5202
|
+
super(`Agent reported failure: ${reason ?? "no reason given"}`);
|
|
5203
|
+
this.reason = reason;
|
|
5204
|
+
}
|
|
5205
|
+
};
|
|
5065
5206
|
function isMissingReportRetry(kind) {
|
|
5066
5207
|
return kind === "missingReport" || kind === "missingReportAtStepLimit";
|
|
5067
5208
|
}
|
|
@@ -5134,6 +5275,7 @@ async function runAgentAttempt(req, attempt, retryReason) {
|
|
|
5134
5275
|
"When you need to read or search multiple files, issue those tool calls together in one step rather than one at a time."
|
|
5135
5276
|
] : [],
|
|
5136
5277
|
"Call notifyUser whenever you start a new phase of work or your focus shifts (e.g. moving from reading code to writing files) \u2014 a short, plain-language, high-level update. Do not call it for every tool use.",
|
|
5278
|
+
STEP_LIMIT_INSTRUCTION,
|
|
5137
5279
|
// Last so a retry does not bust the cached tools+system prefix from
|
|
5138
5280
|
// the first attempt. Inspect-and-continue, not start over: implement
|
|
5139
5281
|
// shares the worktree and toolContext across attempts.
|
|
@@ -5181,7 +5323,11 @@ async function runAgentAttempt(req, attempt, retryReason) {
|
|
|
5181
5323
|
stopWhen: [
|
|
5182
5324
|
hasToolCall("reportStatus"),
|
|
5183
5325
|
stepCountIs(profile.maxSteps)
|
|
5184
|
-
]
|
|
5326
|
+
],
|
|
5327
|
+
prepareStep: ({ stepNumber }) => stepNumber >= profile.maxSteps - 1 ? { activeTools: ["reportStatus"] } : void 0,
|
|
5328
|
+
// Not in the settings type, but forwarded to streamText, whose default prints over the UI.
|
|
5329
|
+
...{ onError: () => {
|
|
5330
|
+
} }
|
|
5185
5331
|
});
|
|
5186
5332
|
const stream = await agent.stream({
|
|
5187
5333
|
prompt: "Follow system instructions"
|
|
@@ -5208,6 +5354,9 @@ async function runAgentAttempt(req, attempt, retryReason) {
|
|
|
5208
5354
|
}
|
|
5209
5355
|
if (streamError !== void 0) {
|
|
5210
5356
|
logger.error({ err: streamError }, "agent stream reported an error");
|
|
5357
|
+
if (isProxyInactivityRejection(streamError)) {
|
|
5358
|
+
throw new Error(SESSION_TIMEOUT_USER_MESSAGE, { cause: streamError });
|
|
5359
|
+
}
|
|
5211
5360
|
throw streamError;
|
|
5212
5361
|
}
|
|
5213
5362
|
const end = Date.now();
|
|
@@ -5257,19 +5406,33 @@ async function runAgentAttempt(req, attempt, retryReason) {
|
|
|
5257
5406
|
}
|
|
5258
5407
|
const result = report.output;
|
|
5259
5408
|
if (result.status !== "success") {
|
|
5260
|
-
|
|
5261
|
-
|
|
5409
|
+
const steps = await stream.steps;
|
|
5410
|
+
logger.warn(
|
|
5411
|
+
{
|
|
5412
|
+
reason: result.reason,
|
|
5413
|
+
stepCount: steps.length,
|
|
5414
|
+
recentSteps: steps.slice(-RECENT_STEPS_LOGGED).map((s) => ({
|
|
5415
|
+
text: s.text?.slice(0, LOGGED_CHARS),
|
|
5416
|
+
toolCalls: s.toolCalls?.map((c) => c.toolName),
|
|
5417
|
+
toolResults: s.toolResults?.map((r) => ({
|
|
5418
|
+
tool: r.toolName,
|
|
5419
|
+
output: JSON.stringify(r.output)?.slice(0, LOGGED_CHARS)
|
|
5420
|
+
}))
|
|
5421
|
+
}))
|
|
5422
|
+
},
|
|
5423
|
+
"agent reported failure"
|
|
5262
5424
|
);
|
|
5425
|
+
throw new AgentReportedFailureError(result.reason);
|
|
5263
5426
|
}
|
|
5264
5427
|
return result.output;
|
|
5265
5428
|
}
|
|
5266
5429
|
|
|
5267
5430
|
// src/actions/detectLanguage.ts
|
|
5268
|
-
import
|
|
5269
|
-
var detectLanguageSchema =
|
|
5270
|
-
languages:
|
|
5271
|
-
frameworks:
|
|
5272
|
-
publicEnvVarPrefix:
|
|
5431
|
+
import z22 from "zod";
|
|
5432
|
+
var detectLanguageSchema = z22.object({
|
|
5433
|
+
languages: z22.array(z22.object({ name: z22.string(), version: z22.string() })),
|
|
5434
|
+
frameworks: z22.array(z22.object({ name: z22.string(), version: z22.string() })),
|
|
5435
|
+
publicEnvVarPrefix: z22.string().regex(/^([A-Z0-9]+_)*$/).describe(
|
|
5273
5436
|
`The prefix a bundler/framework requires for an env var to reach client-side code (e.g. "NEXT_PUBLIC_", "VITE_", "NUXT_PUBLIC_"), uppercase with a trailing underscore when non-empty. Prefer the project's own existing convention over a framework default. Empty string when the project has no client-side env var exposure (e.g. a backend-only project).`
|
|
5274
5437
|
)
|
|
5275
5438
|
});
|
|
@@ -5329,31 +5492,31 @@ var detectLanguage = async () => {
|
|
|
5329
5492
|
};
|
|
5330
5493
|
|
|
5331
5494
|
// src/actions/analyzeCodebase.ts
|
|
5332
|
-
import
|
|
5495
|
+
import z23 from "zod";
|
|
5333
5496
|
var READONLY_TOOLS = [
|
|
5334
5497
|
"listFiles",
|
|
5335
5498
|
"changeDirectory",
|
|
5336
5499
|
"readFile",
|
|
5337
5500
|
"searchFiles"
|
|
5338
5501
|
];
|
|
5339
|
-
var ingestionAnalysisSchema =
|
|
5340
|
-
ingestionAnalysis:
|
|
5341
|
-
|
|
5342
|
-
name:
|
|
5343
|
-
paths:
|
|
5502
|
+
var ingestionAnalysisSchema = z23.object({
|
|
5503
|
+
ingestionAnalysis: z23.array(
|
|
5504
|
+
z23.object({
|
|
5505
|
+
name: z23.string(),
|
|
5506
|
+
paths: z23.array(z23.string()),
|
|
5344
5507
|
// indexable fields the agent found for this entity
|
|
5345
|
-
attributes:
|
|
5508
|
+
attributes: z23.array(z23.string())
|
|
5346
5509
|
})
|
|
5347
5510
|
)
|
|
5348
5511
|
});
|
|
5349
|
-
var searchImplementationAnalysisSchema =
|
|
5350
|
-
searchImplementationAnalysis:
|
|
5512
|
+
var searchImplementationAnalysisSchema = z23.object({
|
|
5513
|
+
searchImplementationAnalysis: z23.string()
|
|
5351
5514
|
});
|
|
5352
|
-
var verificationSchema =
|
|
5353
|
-
verification:
|
|
5515
|
+
var verificationSchema = z23.object({
|
|
5516
|
+
verification: z23.array(z23.string())
|
|
5354
5517
|
});
|
|
5355
5518
|
var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
|
|
5356
|
-
var analyzeCodebaseSchema =
|
|
5519
|
+
var analyzeCodebaseSchema = z23.object({
|
|
5357
5520
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
5358
5521
|
searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
|
|
5359
5522
|
verification: verificationSchema.shape.verification.optional(),
|
|
@@ -5475,8 +5638,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
|
|
|
5475
5638
|
}
|
|
5476
5639
|
|
|
5477
5640
|
// src/actions/confirmLanguage.ts
|
|
5478
|
-
import
|
|
5479
|
-
var confirmLanguageSchema =
|
|
5641
|
+
import z25 from "zod";
|
|
5642
|
+
var confirmLanguageSchema = z25.object({
|
|
5480
5643
|
languages: detectLanguageSchema.shape.languages
|
|
5481
5644
|
});
|
|
5482
5645
|
async function confirmLanguage(ctx) {
|
|
@@ -5497,8 +5660,8 @@ async function confirmLanguage(ctx) {
|
|
|
5497
5660
|
}
|
|
5498
5661
|
|
|
5499
5662
|
// src/actions/confirmFramework.ts
|
|
5500
|
-
import
|
|
5501
|
-
var confirmFrameworkSchema =
|
|
5663
|
+
import z26 from "zod";
|
|
5664
|
+
var confirmFrameworkSchema = z26.object({
|
|
5502
5665
|
frameworks: detectLanguageSchema.shape.frameworks
|
|
5503
5666
|
});
|
|
5504
5667
|
var CURATED_FRAMEWORKS = [
|
|
@@ -5636,8 +5799,8 @@ async function promptUser(ctx, params) {
|
|
|
5636
5799
|
}
|
|
5637
5800
|
|
|
5638
5801
|
// src/actions/confirmEntities.ts
|
|
5639
|
-
import
|
|
5640
|
-
var confirmEntitiesSchema =
|
|
5802
|
+
import z27 from "zod";
|
|
5803
|
+
var confirmEntitiesSchema = z27.object({
|
|
5641
5804
|
// Final detection — the focused re-run may supersede project-scan's.
|
|
5642
5805
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
5643
5806
|
confirmedEntities: confirmedEntitiesFieldSchema,
|
|
@@ -5788,15 +5951,15 @@ async function confirmEntities(ctx) {
|
|
|
5788
5951
|
}
|
|
5789
5952
|
|
|
5790
5953
|
// src/actions/review.ts
|
|
5791
|
-
import { z as
|
|
5792
|
-
var reviewSchema =
|
|
5954
|
+
import { z as z28 } from "zod";
|
|
5955
|
+
var reviewSchema = z28.object({
|
|
5793
5956
|
// Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
|
|
5794
5957
|
// not one entry per workflow step — a step's raw output can be a long,
|
|
5795
5958
|
// multi-paragraph blob (see implement.ts's summaries.join), and mirroring
|
|
5796
5959
|
// that 1:1 is what made the old per-step summary an unreadable wall of text.
|
|
5797
|
-
summaryPoints:
|
|
5798
|
-
reviewPrompt:
|
|
5799
|
-
nextSteps:
|
|
5960
|
+
summaryPoints: z28.array(z28.string()),
|
|
5961
|
+
reviewPrompt: z28.string(),
|
|
5962
|
+
nextSteps: z28.array(z28.string())
|
|
5800
5963
|
});
|
|
5801
5964
|
function formatCompletedSteps(steps) {
|
|
5802
5965
|
if (!steps.length) return "(no prior steps completed)";
|
|
@@ -5831,9 +5994,17 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
5831
5994
|
};
|
|
5832
5995
|
|
|
5833
5996
|
// src/actions/implement.ts
|
|
5834
|
-
import
|
|
5835
|
-
import { access, mkdir as mkdir5, readFile as
|
|
5836
|
-
import {
|
|
5997
|
+
import z29 from "zod";
|
|
5998
|
+
import { access, mkdir as mkdir5, readdir as readdir3, readFile as readFile10 } from "node:fs/promises";
|
|
5999
|
+
import {
|
|
6000
|
+
dirname as dirname5,
|
|
6001
|
+
extname,
|
|
6002
|
+
isAbsolute as isAbsolute4,
|
|
6003
|
+
join as join11,
|
|
6004
|
+
relative as relative7,
|
|
6005
|
+
resolve as resolve5,
|
|
6006
|
+
sep as sep3
|
|
6007
|
+
} from "node:path";
|
|
5837
6008
|
|
|
5838
6009
|
// src/lib/git.ts
|
|
5839
6010
|
import { execFile as execFile3 } from "node:child_process";
|
|
@@ -5913,6 +6084,12 @@ function toRootRelative(p) {
|
|
|
5913
6084
|
return p.replace(/^\/+/, "");
|
|
5914
6085
|
}
|
|
5915
6086
|
|
|
6087
|
+
// src/lib/dashboard.ts
|
|
6088
|
+
function browseUrl(appId, indexName) {
|
|
6089
|
+
const base = appId ? `https://dashboard.algolia.com/apps/${appId}/explorer/browse` : "https://dashboard.algolia.com/explorer/browse";
|
|
6090
|
+
return `${base}/${encodeURIComponent(indexName)}`;
|
|
6091
|
+
}
|
|
6092
|
+
|
|
5916
6093
|
// src/lib/algoliaDocs.ts
|
|
5917
6094
|
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
5918
6095
|
import { dirname as dirname4, join as join10 } from "node:path";
|
|
@@ -5972,36 +6149,36 @@ function getFrameworkSpecificDoc(frameworks) {
|
|
|
5972
6149
|
}
|
|
5973
6150
|
|
|
5974
6151
|
// src/actions/implement.ts
|
|
5975
|
-
var implementSchema =
|
|
5976
|
-
summary:
|
|
5977
|
-
ingestCommand:
|
|
5978
|
-
ingestScriptRan:
|
|
5979
|
-
ingestRecordCount:
|
|
5980
|
-
ingestDurationMs:
|
|
5981
|
-
ingestionSource:
|
|
5982
|
-
searchConfig:
|
|
5983
|
-
filePath:
|
|
5984
|
-
vars:
|
|
5985
|
-
|
|
5986
|
-
name:
|
|
5987
|
-
value:
|
|
6152
|
+
var implementSchema = z29.object({
|
|
6153
|
+
summary: z29.string(),
|
|
6154
|
+
ingestCommand: z29.string().optional(),
|
|
6155
|
+
ingestScriptRan: z29.boolean().optional(),
|
|
6156
|
+
ingestRecordCount: z29.number().optional(),
|
|
6157
|
+
ingestDurationMs: z29.number().optional(),
|
|
6158
|
+
ingestionSource: z29.enum(["local", "fileUpload", "generated"]),
|
|
6159
|
+
searchConfig: z29.object({
|
|
6160
|
+
filePath: z29.string().optional(),
|
|
6161
|
+
vars: z29.array(
|
|
6162
|
+
z29.object({
|
|
6163
|
+
name: z29.string(),
|
|
6164
|
+
value: z29.string()
|
|
5988
6165
|
})
|
|
5989
6166
|
)
|
|
5990
6167
|
}).optional()
|
|
5991
6168
|
});
|
|
5992
|
-
var implementationOutputSchema =
|
|
5993
|
-
summary:
|
|
5994
|
-
ingestCommand:
|
|
6169
|
+
var implementationOutputSchema = z29.object({
|
|
6170
|
+
summary: z29.string(),
|
|
6171
|
+
ingestCommand: z29.string().optional(),
|
|
5995
6172
|
// Only for the search use case: the path of whatever module the agent
|
|
5996
6173
|
// defined the Algolia config constants in, so the wizard can check it
|
|
5997
6174
|
// won't end up gitignored (it's public, meant to be committed).
|
|
5998
|
-
searchConfigFile:
|
|
6175
|
+
searchConfigFile: z29.string().optional()
|
|
5999
6176
|
});
|
|
6000
|
-
var validationOutputSchema =
|
|
6001
|
-
summary:
|
|
6002
|
-
sufficient:
|
|
6003
|
-
additionalInstructions:
|
|
6004
|
-
unrelatedFailure:
|
|
6177
|
+
var validationOutputSchema = z29.object({
|
|
6178
|
+
summary: z29.string(),
|
|
6179
|
+
sufficient: z29.boolean(),
|
|
6180
|
+
additionalInstructions: z29.string().optional(),
|
|
6181
|
+
unrelatedFailure: z29.string().optional()
|
|
6005
6182
|
});
|
|
6006
6183
|
var MAX_IMPLEMENT_VALIDATION_ATTEMPTS = 3;
|
|
6007
6184
|
var FIRST_IMPLEMENTATION_ATTEMPT = 1;
|
|
@@ -6013,9 +6190,14 @@ var INGESTION_SOURCE = {
|
|
|
6013
6190
|
fileUpload: "Import a data file (CSV, JSON, TSV)",
|
|
6014
6191
|
generated: "Generate sample data"
|
|
6015
6192
|
};
|
|
6016
|
-
var BUILD_CHECK_PROMPT = "Run type-check
|
|
6017
|
-
var MISSING_DEPENDENCIES_PROMPT = "Dependencies are not installed. Install them and run type-check
|
|
6193
|
+
var BUILD_CHECK_PROMPT = "Run type-check now? This can take several minutes on a large repository.";
|
|
6194
|
+
var MISSING_DEPENDENCIES_PROMPT = "Dependencies are not installed. Install them and run type-check now?";
|
|
6018
6195
|
var UNRELATED_FAILURE_PROMPT = "These checks are still failing and appear unrelated to the changes. Continue the investigation?";
|
|
6196
|
+
var INGEST_RETRY_PROMPT = "Fix the cause (for example, start the database the script reads from), then retry.";
|
|
6197
|
+
var INGEST_RETRY = {
|
|
6198
|
+
retry: "Retry ingestion",
|
|
6199
|
+
skip: "Continue without records"
|
|
6200
|
+
};
|
|
6019
6201
|
var FILE_UPLOAD_PATH_PROMPT = "Enter the path to your JSON, CSV, or TSV file (relative to the project root, or absolute):";
|
|
6020
6202
|
var JS_LANGUAGES = ["javascript", "typescript", "jsx", "tsx", "node"];
|
|
6021
6203
|
function lower(entries) {
|
|
@@ -6053,10 +6235,12 @@ function baseInstructions(input) {
|
|
|
6053
6235
|
`Project languages and frameworks: ${JSON.stringify(input.language)}`,
|
|
6054
6236
|
"Make minimal, idiomatic changes; do not touch unrelated code.",
|
|
6055
6237
|
`Commands run through a shell on ${process.platform}. Write commands that work there.`,
|
|
6056
|
-
"Use the project's own tooling for every command \u2014 its package manager
|
|
6238
|
+
"Use the project's own tooling for every command \u2014 its package manager and task runner.",
|
|
6239
|
+
"Never run a linter or formatter, and do not fix lint or formatting issues. The developer did not ask for them.",
|
|
6057
6240
|
"runShell needs the developer to approve each command, so give every call a clear `explanation` naming what it does and any side effect. If a command is rejected, do not retry it \u2014 take a different approach or report the limitation."
|
|
6058
6241
|
];
|
|
6059
6242
|
}
|
|
6243
|
+
var FILE_EDIT_INSTRUCTION = "To change an existing file, call editFile with only the changed part. Use writeFile only to create a file; never rewrite an existing file with writeFile.";
|
|
6060
6244
|
function sourceSpecificInstructions(input) {
|
|
6061
6245
|
const byLine = {
|
|
6062
6246
|
local: [
|
|
@@ -6089,15 +6273,23 @@ function algoliaClientDoc(input) {
|
|
|
6089
6273
|
doc
|
|
6090
6274
|
];
|
|
6091
6275
|
}
|
|
6276
|
+
var LARGE_RECORD_PLAN = /^(grow|elevate|premium)/i;
|
|
6277
|
+
function recordSizeInstruction(plan) {
|
|
6278
|
+
const limit = plan && LARGE_RECORD_PLAN.test(plan.trim()) ? "Algolia rejects a record larger than 100 KB with a status 400 error, and the average record size in the index must stay under 10 KB. Keep every record well under 100 KB and the average under 10 KB" : "Algolia rejects a record larger than 10 KB with a status 400 error. Keep every record under 10 KB";
|
|
6279
|
+
return `${limit}: split long text fields (for example an article body) into several records that share a parent identifier, or truncate them. Never push the whole text of a long document as one record. Check the size of the largest source item before you write the script.`;
|
|
6280
|
+
}
|
|
6092
6281
|
function ingestionInstructions(input) {
|
|
6093
6282
|
return [
|
|
6094
6283
|
...input.confirmed && input.confirmed.length ? [
|
|
6095
6284
|
`Create an ingestion script under "${input.ingestDir}/" at the repo root. That directory already exists \u2014 writeFile creates any nested path itself, so never run a shell command just to create a directory.`,
|
|
6285
|
+
FILE_EDIT_INSTRUCTION,
|
|
6096
6286
|
`Ingest only the confirmed entity (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
6097
6287
|
`Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them. The wizard sets these when it runs the script.`,
|
|
6098
6288
|
`Read the index name from the ${INDEX_NAME_VAR} environment variable, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or entity name \u2014 the write key only works for that exact index. Exit with an error if ${INDEX_NAME_VAR} is unset.`,
|
|
6099
6289
|
"Write the script in the project's primary language, using Algolia's official client for that language. Do not use the raw HTTP API.",
|
|
6100
6290
|
"After a successful ingest, the script must print exactly one line to stdout in the form `ALGOLIA_WIZARD_RECORD_COUNT=<n>`, where <n> is the total number of records pushed to Algolia. Print it last, on its own line, with no surrounding text.",
|
|
6291
|
+
recordSizeInstruction(input.appPlan),
|
|
6292
|
+
"When you split one source item into several records, keep the fields that the search UI shows (title, description, URL parts) on every record. After you push the records, the script must set the index settings with attributeForDistinct set to the parent identifier and distinct set to true, so that a search returns each source item once.",
|
|
6101
6293
|
...algoliaClientDoc(input),
|
|
6102
6294
|
"Install the Algolia client with the project's own package manager via runShell, declaring it in whatever manifest the project uses (e.g. package.json, requirements.txt, Gemfile, go.mod, composer.json) so the dependency is not just installed ad hoc.",
|
|
6103
6295
|
`If the script loads its env vars from a file (e.g. via dotenv or an equivalent for its language) rather than the process environment directly, decide that up front and call writeCredentials on that file before you finish the script \u2014 do not wait to discover the need for it by having a writeFile call refused.`,
|
|
@@ -6108,6 +6300,7 @@ function ingestionInstructions(input) {
|
|
|
6108
6300
|
] : [],
|
|
6109
6301
|
`When the script is finished, decide the exact command and cwd you will use to run it. Make sure that the script path in the command resolves from that cwd to the filePath. Then call reviewScript with that filePath, command, cwd, the exact selected entityName, and usesGeneratedRecords set to ${input.ingestionSource === "generated"}. Wait for the developer to review it because running it writes records to a live index. Do not run it before that call returns. If you change the command or cwd after a failed run, call reviewScript again before runShell.`,
|
|
6110
6302
|
'Then, without changing directories, run the script yourself via runShell using the exact same command and cwd. Report the command you ran as "ingestCommand" so the developer can re-run it. Its explanation must say that running it writes records to Algolia.',
|
|
6303
|
+
"When the script reads from a database or another service, find how the project connects to it locally (env files, README, socket or TCP settings). If that connection needs a local service, such as a database proxy (for example cloud-sql-proxy) or a tunnel, make the script check the connection before it reads, and exit with an error that names the service and the command from the project docs that starts it.",
|
|
6111
6304
|
"If external data access fails, do not run authentication commands, change accounts or machine configuration, or start proxies, containers, or services.",
|
|
6112
6305
|
"Do not troubleshoot unavailable external infrastructure. Preserve the script and command, report success, and let the developer prepare the data source.",
|
|
6113
6306
|
"If the script cannot access an external data source because credentials or a service are unavailable, do not report status=fail. Report status=success with the completed script, ingestCommand, and a concise failure summary. The wizard handles this run failure and tells the developer how to retry.",
|
|
@@ -6127,7 +6320,7 @@ function searchInstructions(input) {
|
|
|
6127
6320
|
const placementMessage = "Place content-specific search beside the heading or controls for the corresponding browse/listing task. Use shared navigation only for genuinely app-wide search and only when it fits the available space and responsive layout without crowding primary actions.";
|
|
6128
6321
|
const replacementMessage = "If a search control with the same content scope and purpose already exists, replace its usage with the new component while preserving its intentional placement, and delete the implementation it supersedes. Do not remove or replace unrelated filters, navigation, or differently scoped search.";
|
|
6129
6322
|
const accessibilityInstructions = [
|
|
6130
|
-
"Target WCAG 2.2 AA. Use semantic search structure and give the input a persistent accessible name: prefer a visible associated label and use the project's visually-hidden utility where the layout has no room for one. A placeholder or a labeled search landmark does not name the input. When the input uses aria-labelledby, every referenced element must exist and contain meaningful, content-specific text. Preserve a widget's generated label when it provides one and make it content-specific through supported options
|
|
6323
|
+
"Target WCAG 2.2 AA. Use semantic search structure and give the input a persistent accessible name: prefer a visible associated label and use the project's visually-hidden utility where the layout has no room for one. A placeholder or a labeled search landmark does not name the input. When the input uses aria-labelledby, every referenced element must exist and contain meaningful, content-specific text. Preserve a widget's generated label when it provides one and make it content-specific through supported options. If the SDK reference says that no option changes the generated label, accept that label as a known widget limitation: do not rebuild the widget headless, do not patch generated DOM, and name the gap in the summary.",
|
|
6131
6324
|
"Preserve widget-provided combobox, popup, and item roles, relationships, and keyboard behavior; for a headless widget, implement the documented semantics instead of inventing different ARIA. Tab must reach the input and controls, Arrow keys move through results, Enter selects, and Escape dismisses without trapping or losing focus.",
|
|
6132
6325
|
"Meet 4.5:1 contrast for normal text, including input values, placeholders, and result text, and 3:1 for large text, meaningful icons, control boundaries, and focus indicators against adjacent colors. Keep focus visible and unobscured, and never communicate active, loading, empty, or error states by color alone.",
|
|
6133
6326
|
"Keep loading, empty, and error status text perceivable to assistive technology. Do not assume a widget announces these states: use documented announcements when present, otherwise add a persistent polite live region driven by the current search status, results, and errors.",
|
|
@@ -6139,22 +6332,66 @@ function searchInstructions(input) {
|
|
|
6139
6332
|
indexedContentMessage ?? "",
|
|
6140
6333
|
`Build the search UI for ${input.searchUiTarget}.`,
|
|
6141
6334
|
"Create search UI only. Do not create or modify ingestion scripts, rake/manage/CLI tasks, migrations, seeders, or other data-loading code, even if the data looks incomplete.",
|
|
6335
|
+
FILE_EDIT_INSTRUCTION,
|
|
6336
|
+
EXISTING_COMPONENT_INSTRUCTION,
|
|
6142
6337
|
mountCandidateMessage,
|
|
6143
6338
|
placementMessage,
|
|
6144
6339
|
replacementMessage,
|
|
6145
6340
|
"Inspect the chosen mount area at the app's existing breakpoints before writing CSS. Preserve its visual hierarchy, alignment, spacing, and primary actions rather than forcing the search control into an arbitrary gap.",
|
|
6146
6341
|
...packageManagedInstructions,
|
|
6342
|
+
CSP_INSTRUCTION,
|
|
6147
6343
|
...accessibilityInstructions,
|
|
6148
6344
|
"Style the search surfaces through the widget's class and CSS-variable overrides (or, when hand-built, the app's existing theme tokens): give the results panel and the element that actually draws the input's border and background \u2014 for a widget, the form or wrapper it renders around the bare input \u2014 non-transparent surfaces, keep that frame distinguishable from the background behind it in every supported theme, and never assume a light surface or reuse the surrounding page's colors unchanged inside the panel.",
|
|
6149
6345
|
"The panel takes its width from the input by default, so a small input makes it unreadably narrow: give it `min-width: min(320px, calc(100vw - 2rem))` independent of the input, cap its maximum width to the viewport, and anchor it to the input edge it opens from so widening does not push it off-screen.",
|
|
6150
6346
|
"Match the application styles closely, including light, dark, and high-contrast themes the project already supports.",
|
|
6151
|
-
"Before reporting, mount the component
|
|
6347
|
+
"Before reporting, mount the component. Do not run a type check, build, or test command: the validation pass runs the type check, after the developer agrees to it. If a tool limit is reached or an error remains unresolved, keep the implementation, report status=success, and name the remaining issue in the summary so validation can drive the next pass. Report status=fail only when no search implementation can be written, with the concrete blocker as the reason.",
|
|
6152
6348
|
"The summary should be extremely concise; do not mention manual testing steps."
|
|
6153
6349
|
];
|
|
6154
6350
|
}
|
|
6351
|
+
var EXISTING_COMPONENT_INSTRUCTION = "A search component for this content can already exist, for example from an earlier run. Do not trust it: compare the record fields that it reads with the real records, and rewrite it when they differ. Do not report success while the UI reads a field that no record has.";
|
|
6352
|
+
var CSP_INSTRUCTION = 'Search for a Content-Security-Policy (next.config, middleware, proxy, server headers, or a meta tag). If the project sets one, it blocks Algolia until you allow it: add https://*.algolia.net, https://*.algolianet.com, and https://insights.algolia.io to connect-src, and load search-insights from npm as the SDK reference describes instead of from jsDelivr. Without this, every search fails with "Unreachable hosts" and the widget stays empty.';
|
|
6353
|
+
var INGEST_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
6354
|
+
".sh",
|
|
6355
|
+
".py",
|
|
6356
|
+
".js",
|
|
6357
|
+
".mjs",
|
|
6358
|
+
".cjs",
|
|
6359
|
+
".ts",
|
|
6360
|
+
".rb",
|
|
6361
|
+
".php",
|
|
6362
|
+
".go"
|
|
6363
|
+
]);
|
|
6364
|
+
async function findIngestionScript(repoRoot, ingestDir, ingestCommand) {
|
|
6365
|
+
const fromCommand = ingestCommand?.split(/\s+/).map((token) => token.replace(/^["']|["']$/g, "")).find((token) => token.startsWith(`${ingestDir}/`));
|
|
6366
|
+
if (fromCommand) {
|
|
6367
|
+
try {
|
|
6368
|
+
await access(join11(repoRoot, fromCommand));
|
|
6369
|
+
return fromCommand;
|
|
6370
|
+
} catch {
|
|
6371
|
+
}
|
|
6372
|
+
}
|
|
6373
|
+
try {
|
|
6374
|
+
const entries = await readdir3(join11(repoRoot, ingestDir), {
|
|
6375
|
+
withFileTypes: true
|
|
6376
|
+
});
|
|
6377
|
+
const name = entries.filter(
|
|
6378
|
+
(entry) => entry.isFile() && !entry.name.startsWith(".") && INGEST_SCRIPT_EXTENSIONS.has(extname(entry.name))
|
|
6379
|
+
).map((entry) => entry.name).sort()[0];
|
|
6380
|
+
return name && `${ingestDir}/${name}`;
|
|
6381
|
+
} catch {
|
|
6382
|
+
return void 0;
|
|
6383
|
+
}
|
|
6384
|
+
}
|
|
6385
|
+
function recordSourceInstruction(scriptPath) {
|
|
6386
|
+
return `The records in the index come from the ingestion script "${scriptPath}" (repo root). Read that one file to learn each record's objectID, its fields, and how they map to the app's data and URLs. Do not search the backend, api, or scripts folders for them, and do not use shell reads (sed, grep, cat) to explore. Read the fields with their exact attribute names from the records: a dotted name such as "copy.en.title" is one flat attribute, so read it as item["copy.en.title"]. Use only the fields of the current locale. When the script splits one item into several records that share a parent identifier, the index returns each item once, so show one result per item.`;
|
|
6387
|
+
}
|
|
6388
|
+
function recordFieldCheckInstruction(scriptPath) {
|
|
6389
|
+
return `Read the ingestion script "${scriptPath}" (repo root). Check that every record field that the search UI reads (title, description, link parts) exists in the records with that exact attribute name. A dotted name such as "copy.en.title" is one flat attribute. If the UI reads a field that no record has, set sufficient=false and name the wrong field and the right one in additionalInstructions. Do not use shell reads to explore.`;
|
|
6390
|
+
}
|
|
6155
6391
|
function packageSearchInstructions(input) {
|
|
6156
6392
|
const doc = getFrameworkSpecificDoc(frameworksForDoc(input.language));
|
|
6157
6393
|
return [
|
|
6394
|
+
"To check an installed package version, call readFile on node_modules/<package>/package.json. Do not otherwise explore node_modules, and do not run node -e or other scripts to discover a package API. If the reference below does not cover a detail, choose the documented option and name the gap in the summary.",
|
|
6158
6395
|
...doc ? [
|
|
6159
6396
|
"Follow the Algolia SDK reference below for client setup, search UI wiring, and Insights instrumentation \u2014 Insights is required, not optional; prefer the reference over prior knowledge:",
|
|
6160
6397
|
doc
|
|
@@ -6188,25 +6425,30 @@ function validationInstructions(input) {
|
|
|
6188
6425
|
"Validate the Algolia search implementation by inspecting the project source.",
|
|
6189
6426
|
`Indexed content context: ${JSON.stringify(input.findings.confirmedEntities?.[0] ?? null)}.`,
|
|
6190
6427
|
`The confirmed placement candidate was "${input.searchLocation ?? "unknown"}", but verify the actual mount rather than assuming the candidate was used.`,
|
|
6191
|
-
"Inspect the generated search component, its actual mount file, that file's parent layout and sibling controls, and the relevant content UI. Do not rely only on
|
|
6428
|
+
"Inspect the generated search component, its actual mount file, that file's parent layout and sibling controls, and the relevant content UI. Do not rely only on type checks or tests.",
|
|
6192
6429
|
"Confirm that the application renders exactly one search bar. Check for an old search bar and duplicate mounts.",
|
|
6430
|
+
"If the project sets a Content-Security-Policy, confirm that connect-src allows https://*.algolia.net, https://*.algolianet.com, and https://insights.algolia.io, and that search-insights is loaded from npm. Otherwise set sufficient=false.",
|
|
6193
6431
|
"Confirm content-specific search is placed with the corresponding content or task controls. Shared navigation is valid only for genuinely app-wide search that fits the visual hierarchy and responsive layout without crowding primary actions.",
|
|
6194
6432
|
"Confirm the input has a persistent accessible name; widget-provided combobox, popup, and item relationships and keyboard behavior are preserved; and loading, empty, and error status text is perceivable to assistive technology.",
|
|
6195
6433
|
"Inspect the CSS that actually applies to the generated DOM. Confirm the results panel and the element drawing the input frame have non-transparent surfaces, the input frame is distinguishable from its surrounding background in every supported theme, text meets 4.5:1 contrast, meaningful boundaries and focus indicators meet 3:1, and no state relies on color alone.",
|
|
6196
6434
|
"Confirm the search experience reflows without horizontal page scrolling at 320 CSS pixels, keeps its panel on-screen, preserves visible focus, and respects reduced-motion preferences for any added animation.",
|
|
6435
|
+
"If the project imports an InstantSearch theme and overrides its palette, confirm that the palette overrides (--ais-*-rgb) win over the theme: they must use a higher specificity than the theme, for example `:root:root`, and for a dark mode `:root:root.dark` or `:root:root[data-theme=dark]`, because the theme sets its own blue in `:root` and in `:root[data-theme=dark], .dark`. `:root:root` alone ties with `:root[data-theme=dark]`. If an override can lose to the theme, set sufficient=false, because the search icon and focus ring then show the theme blue instead of the app accent color.",
|
|
6436
|
+
"When the project has a package.json, confirm that the theme CSS of the search widget (for InstantSearch, a theme from instantsearch.css) is imported once, from the component or the global stylesheet. If it is installed but not imported, set sufficient=false.",
|
|
6197
6437
|
"Confirm that the search component is reachable from and fits the corresponding content task at every supported breakpoint; require app-wide reachability only when the search scope is genuinely app-wide.",
|
|
6198
6438
|
"Do not run the test suite or any test command.",
|
|
6199
6439
|
"Do not modify files. Report each implementation issue as a concrete instruction for the next implementation pass.",
|
|
6200
|
-
`
|
|
6440
|
+
`Detected verification tools: ${JSON.stringify(input.findings.verification ?? [])}. Use only a type checker; ignore any linter or formatter in this list.`,
|
|
6201
6441
|
...!input.frontendHasPackageJson ? [
|
|
6202
6442
|
"The frontend has no package.json: never create one or run npm, pnpm, yarn, or bun. Validate the CDN implementation by inspecting its HTML or template and classic scripts for the search container, Algolia \u2192 InstantSearch \u2192 config \u2192 search load order, browser globals, autocomplete widget configuration, search.start(), and exact pinned CDN URLs and integrity attributes for every Algolia script or stylesheet. Source inspection is required even when the project has no automated checks. window.instantsearch.widgets.autocomplete is a real widget in the pinned InstantSearch build; do not report its use as an error, and do not fetch a remote bundle or URL to verify it."
|
|
6203
6443
|
] : [],
|
|
6204
6444
|
"Always call reportStatus with status=success after validation, even when sufficient=false.",
|
|
6205
6445
|
"Set sufficient=false if the input's computed accessible name is empty or generic, including when aria-labelledby references a missing or empty element; a placeholder does not count as the label.",
|
|
6446
|
+
'Exception: the InstantSearch Autocomplete widget (react-instantsearch or instantsearch.js) names its input "Submit" through its generated label (the input has aria-labelledby pointing at that label), and no widget option changes it. Accept that name when the widget is used unmodified, and do not ask for an aria-label, inputProps, or a label wrapper: do not set sufficient=false for it, and do not request a headless rebuild or DOM patch.',
|
|
6206
6447
|
"Set sufficient=true when the search bar is unique and structurally complete and no placement or accessibility violation was found.",
|
|
6207
6448
|
"Set sufficient=false when a required placement or accessibility property is observed to fail, the implementation is incomplete, or checks have implementation-caused failures; include concrete additionalInstructions for the next pass.",
|
|
6208
|
-
"If
|
|
6209
|
-
"If a check fails only in untouched code, set unrelatedFailure to a concise failure description."
|
|
6449
|
+
"You can only read source files. If a required property can be checked in the source, check it there. If it can be established only in a browser (rendered contrast, keyboard behavior, 320-pixel reflow, live-region status, computed accessible name), set sufficient=true when the source shows no violation. Never request devtools, a screenshot, or a contrast tool, and never repeat a finding that an earlier pass already fixed.",
|
|
6450
|
+
"If a check fails only in untouched code, set unrelatedFailure to a concise failure description.",
|
|
6451
|
+
"Keep the summary to one short sentence. Put the details in additionalInstructions."
|
|
6210
6452
|
];
|
|
6211
6453
|
}
|
|
6212
6454
|
var IMPLEMENT_CONFIG = {
|
|
@@ -6227,12 +6469,19 @@ var useCaseToolMap = {
|
|
|
6227
6469
|
ingestion: [
|
|
6228
6470
|
...FS_READ_TOOLS,
|
|
6229
6471
|
"writeFile",
|
|
6472
|
+
"editFile",
|
|
6230
6473
|
"writeCredentials",
|
|
6231
6474
|
"runShell",
|
|
6232
6475
|
"reviewScript",
|
|
6233
6476
|
"notifyUser"
|
|
6234
6477
|
],
|
|
6235
|
-
search: [
|
|
6478
|
+
search: [
|
|
6479
|
+
...FS_READ_TOOLS,
|
|
6480
|
+
"writeFile",
|
|
6481
|
+
"editFile",
|
|
6482
|
+
"runShell",
|
|
6483
|
+
"notifyUser"
|
|
6484
|
+
],
|
|
6236
6485
|
validation: [...FS_READ_TOOLS, "runShell", "notifyUser"]
|
|
6237
6486
|
};
|
|
6238
6487
|
function toolsForUseCase(useCase, ingestionSource) {
|
|
@@ -6306,7 +6555,7 @@ function jsTypeName(value) {
|
|
|
6306
6555
|
}
|
|
6307
6556
|
async function firstRecordIn(filePath) {
|
|
6308
6557
|
try {
|
|
6309
|
-
const parsed = JSON.parse(await
|
|
6558
|
+
const parsed = JSON.parse(await readFile10(filePath, "utf8"));
|
|
6310
6559
|
const first = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
6311
6560
|
if (first && typeof first === "object" && !Array.isArray(first)) {
|
|
6312
6561
|
return first;
|
|
@@ -6353,6 +6602,18 @@ function ingestFailure(attempt, executions) {
|
|
|
6353
6602
|
}
|
|
6354
6603
|
return { reason: `it failed (exit ${attempt.exitCode ?? "unknown"})`, detail };
|
|
6355
6604
|
}
|
|
6605
|
+
function ingestFailureMessages(headline, detail, command, cwd) {
|
|
6606
|
+
return [
|
|
6607
|
+
headline,
|
|
6608
|
+
// One message per line: Notices.tsx counts a message as one wrapped
|
|
6609
|
+
// line, so an embedded newline overflows the panel's height accounting.
|
|
6610
|
+
...detail?.split("\n").filter((l) => l.trim()) ?? [],
|
|
6611
|
+
...command ? [
|
|
6612
|
+
`Ingestion command: ${command}`,
|
|
6613
|
+
...cwd !== "." ? [`Working directory: ${cwd}`] : []
|
|
6614
|
+
] : []
|
|
6615
|
+
];
|
|
6616
|
+
}
|
|
6356
6617
|
function makeToolContext(root, appId, env = async () => ({}), secretEnv = async () => ({})) {
|
|
6357
6618
|
return {
|
|
6358
6619
|
...createToolContext(
|
|
@@ -6363,6 +6624,12 @@ function makeToolContext(root, appId, env = async () => ({}), secretEnv = async
|
|
|
6363
6624
|
appId
|
|
6364
6625
|
};
|
|
6365
6626
|
}
|
|
6627
|
+
var ERROR_SUMMARY_MAX_CHARS = 200;
|
|
6628
|
+
function briefSummary(summary) {
|
|
6629
|
+
const firstLine = summary.trim().split("\n")[0] ?? "";
|
|
6630
|
+
if (!firstLine) return "no summary given";
|
|
6631
|
+
return firstLine.length > ERROR_SUMMARY_MAX_CHARS ? `${firstLine.slice(0, ERROR_SUMMARY_MAX_CHARS - 1)}\u2026` : firstLine;
|
|
6632
|
+
}
|
|
6366
6633
|
function validationRetryInstructions(validation) {
|
|
6367
6634
|
return [
|
|
6368
6635
|
`Implementation insufficient. Address these findings before reporting completion: ${validation.additionalInstructions ?? validation.summary}`
|
|
@@ -6420,7 +6687,7 @@ function installCheckInstructions(plan, attempt) {
|
|
|
6420
6687
|
}
|
|
6421
6688
|
return ["Dependencies were already installed. Do not install them again."];
|
|
6422
6689
|
}
|
|
6423
|
-
function validationRunInstructions(plan,
|
|
6690
|
+
function validationRunInstructions(plan, attempt) {
|
|
6424
6691
|
if (!plan.run) {
|
|
6425
6692
|
return [
|
|
6426
6693
|
"The developer chose to skip build checks. Do not install dependencies or run shell checks."
|
|
@@ -6431,7 +6698,6 @@ function validationRunInstructions(plan, changedFiles, attempt) {
|
|
|
6431
6698
|
...installCheckInstructions(plan, attempt),
|
|
6432
6699
|
"Do not use npx, dlx, or another package runner that can download an undeclared tool.",
|
|
6433
6700
|
"Run the type check once with the project's existing command, when one is available.",
|
|
6434
|
-
`Lint only these changed files: ${JSON.stringify(changedFiles)}. Do not run a full-project lint. If this list is empty, do not run lint.`,
|
|
6435
6701
|
"Do not use an autofix option during validation."
|
|
6436
6702
|
];
|
|
6437
6703
|
}
|
|
@@ -6526,11 +6792,16 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
|
6526
6792
|
const searchLocation = findings.searchImplementationAnalysis;
|
|
6527
6793
|
let appId;
|
|
6528
6794
|
let ingestAppId;
|
|
6795
|
+
let appPlan;
|
|
6529
6796
|
if (useCases.includes("search")) {
|
|
6530
|
-
|
|
6797
|
+
const app = await requireApplication(ctx.appId);
|
|
6798
|
+
appId = app.id;
|
|
6799
|
+
appPlan = app.plan;
|
|
6531
6800
|
}
|
|
6532
6801
|
if (useCases.includes("ingestion")) {
|
|
6533
|
-
|
|
6802
|
+
const app = appId ? void 0 : await requireApplication(ctx.appId);
|
|
6803
|
+
ingestAppId = appId ?? app?.id;
|
|
6804
|
+
appPlan ??= app?.plan;
|
|
6534
6805
|
}
|
|
6535
6806
|
let ingestWriteKey;
|
|
6536
6807
|
if (useCases.includes("ingestion") && ingestAppId) {
|
|
@@ -6564,6 +6835,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
|
6564
6835
|
targetIndex,
|
|
6565
6836
|
language,
|
|
6566
6837
|
appId,
|
|
6838
|
+
appPlan,
|
|
6567
6839
|
searchKey,
|
|
6568
6840
|
searchKeyError,
|
|
6569
6841
|
ingestDir: INGEST_DIR,
|
|
@@ -6577,6 +6849,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
|
6577
6849
|
};
|
|
6578
6850
|
let agentRuns = 0;
|
|
6579
6851
|
let ingestCommand;
|
|
6852
|
+
let ingestionScript;
|
|
6580
6853
|
let ingestScriptRan = false;
|
|
6581
6854
|
let ingestRecordCount;
|
|
6582
6855
|
let ingestDurationMs;
|
|
@@ -6596,18 +6869,31 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
|
6596
6869
|
async function runImplementationUseCase(currentUseCase, extraInstructions = [], isRetry = false) {
|
|
6597
6870
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
6598
6871
|
agentRuns += 1;
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
currentUseCase
|
|
6603
|
-
input,
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6872
|
+
const toolContext = currentUseCase === "ingestion" ? ingestionTools : makeToolContext(repoRoot);
|
|
6873
|
+
try {
|
|
6874
|
+
return await runAgent({
|
|
6875
|
+
operation: `${currentUseCase}-implementation`,
|
|
6876
|
+
instructions: buildAgentInstructions(currentUseCase, input, [
|
|
6877
|
+
...currentUseCase === "search" && ingestionScript ? [recordSourceInstruction(ingestionScript)] : [],
|
|
6878
|
+
...extraInstructions
|
|
6879
|
+
]),
|
|
6880
|
+
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
6881
|
+
outputSchema: implementationOutputSchema,
|
|
6882
|
+
modelProfile: isRetry ? "implementationRetry" /* implementationRetry */ : "implementation" /* implementation */,
|
|
6883
|
+
toolContext
|
|
6884
|
+
});
|
|
6885
|
+
} catch (err) {
|
|
6886
|
+
if (currentUseCase === "search" && err instanceof AgentReportedFailureError && readLimitReached(toolContext)) {
|
|
6887
|
+
logger.warn(
|
|
6888
|
+
{ reason: err.reason, counts: toolContext.counts },
|
|
6889
|
+
"implement: search agent reported fail at a read limit; continuing to validation"
|
|
6890
|
+
);
|
|
6891
|
+
return {
|
|
6892
|
+
summary: "Stopped at a read limit before the implementation was complete."
|
|
6893
|
+
};
|
|
6894
|
+
}
|
|
6895
|
+
throw err;
|
|
6896
|
+
}
|
|
6611
6897
|
}
|
|
6612
6898
|
async function runValidationUseCase(extraInstructions) {
|
|
6613
6899
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
@@ -6619,6 +6905,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
|
6619
6905
|
operation: "search-validation",
|
|
6620
6906
|
instructions: buildAgentInstructions("validation", input, [
|
|
6621
6907
|
writtenFiles.length ? `Files written during search implementation: ${JSON.stringify(writtenFiles)}. Inspect these first, then trace the actual import and render site.` : "The search agent reported no written files. Inspect the current diff and set sufficient=false if no complete mounted search implementation exists.",
|
|
6908
|
+
...ingestionScript ? [recordFieldCheckInstruction(ingestionScript)] : [],
|
|
6622
6909
|
...extraInstructions
|
|
6623
6910
|
]),
|
|
6624
6911
|
tools: toolsForUseCase("validation"),
|
|
@@ -6634,11 +6921,45 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
|
6634
6921
|
ingestCommand = result.ingestCommand;
|
|
6635
6922
|
const ingestionContext = ingestionTools;
|
|
6636
6923
|
const executions = ingestionContext.shell.executions;
|
|
6924
|
+
let outcome = ingestOutcome(executions, repoRoot, ingestCommand);
|
|
6925
|
+
let failureAcknowledged = false;
|
|
6926
|
+
while (!outcome.run && outcome.attempt?.approved) {
|
|
6927
|
+
const attempt = outcome.attempt;
|
|
6928
|
+
const { reason, detail } = ingestFailure(attempt, executions);
|
|
6929
|
+
const choice = await ctx.requestUserInput({
|
|
6930
|
+
prompt: INGEST_RETRY_PROMPT,
|
|
6931
|
+
promptType: "multipleChoice",
|
|
6932
|
+
options: [INGEST_RETRY.retry, INGEST_RETRY.skip],
|
|
6933
|
+
messages: ingestFailureMessages(
|
|
6934
|
+
`\u26A0\uFE0F Records were not indexed \u2014 ${reason}.`,
|
|
6935
|
+
detail,
|
|
6936
|
+
attempt.command,
|
|
6937
|
+
relativeIngestCwd(attempt, repoRoot)
|
|
6938
|
+
)
|
|
6939
|
+
});
|
|
6940
|
+
if (choice !== INGEST_RETRY.retry) {
|
|
6941
|
+
failureAcknowledged = true;
|
|
6942
|
+
break;
|
|
6943
|
+
}
|
|
6944
|
+
const rerun = await runReviewedCommand(
|
|
6945
|
+
ingestionContext,
|
|
6946
|
+
attempt.command,
|
|
6947
|
+
attempt.cwd
|
|
6948
|
+
);
|
|
6949
|
+
if (rerun === void 0 || typeof rerun === "string") {
|
|
6950
|
+
logger.warn(
|
|
6951
|
+
{ command: attempt.command, refusal: rerun },
|
|
6952
|
+
"implement: could not retry the ingestion command"
|
|
6953
|
+
);
|
|
6954
|
+
break;
|
|
6955
|
+
}
|
|
6956
|
+
outcome = ingestOutcome(executions, repoRoot, ingestCommand);
|
|
6957
|
+
}
|
|
6637
6958
|
const {
|
|
6638
6959
|
run: ingestRun,
|
|
6639
6960
|
attempt: ingestAttempt,
|
|
6640
6961
|
recordCount
|
|
6641
|
-
} =
|
|
6962
|
+
} = outcome;
|
|
6642
6963
|
let ingestCwd = ".";
|
|
6643
6964
|
if (ingestAttempt) {
|
|
6644
6965
|
ingestCommand = ingestAttempt.command;
|
|
@@ -6714,31 +7035,31 @@ ${detail}` : ""}`
|
|
|
6714
7035
|
messages: [
|
|
6715
7036
|
ingestOutcomeMessage,
|
|
6716
7037
|
"View your ingested records in the Algolia dashboard:",
|
|
6717
|
-
|
|
7038
|
+
browseUrl(ingestAppId, targetIndex),
|
|
6718
7039
|
"When you're ready to continue, we'll move on to search implementation."
|
|
6719
7040
|
]
|
|
6720
7041
|
});
|
|
6721
|
-
} else {
|
|
6722
|
-
const commandMessages = ingestCommand ? [
|
|
6723
|
-
`Ingestion command: ${ingestCommand}`,
|
|
6724
|
-
...ingestCwd !== "." ? [`Working directory: ${ingestCwd}`] : []
|
|
6725
|
-
] : [];
|
|
7042
|
+
} else if (!failureAcknowledged) {
|
|
6726
7043
|
await ctx.requestUserInput({
|
|
6727
7044
|
prompt: "",
|
|
6728
7045
|
promptType: "enterToContinue",
|
|
6729
7046
|
options: [],
|
|
6730
|
-
|
|
6731
|
-
// line, so an embedded newline overflows the panel's height accounting.
|
|
6732
|
-
messages: [
|
|
7047
|
+
messages: ingestFailureMessages(
|
|
6733
7048
|
ingestOutcomeMessage,
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
7049
|
+
ingestFailureDetail,
|
|
7050
|
+
ingestCommand,
|
|
7051
|
+
ingestCwd
|
|
7052
|
+
)
|
|
6737
7053
|
});
|
|
6738
7054
|
}
|
|
6739
7055
|
}
|
|
6740
7056
|
let searchConfigFile;
|
|
6741
7057
|
if (useCases.includes("search")) {
|
|
7058
|
+
ingestionScript = await findIngestionScript(
|
|
7059
|
+
repoRoot,
|
|
7060
|
+
INGEST_DIR,
|
|
7061
|
+
ingestCommand
|
|
7062
|
+
);
|
|
6742
7063
|
let extraInstructions = [];
|
|
6743
7064
|
let buildCheckPlan;
|
|
6744
7065
|
const confirmedFrameworks = language.frameworks ?? [];
|
|
@@ -6786,11 +7107,8 @@ ${detail}` : ""}`
|
|
|
6786
7107
|
searchConfigFile = searchResult.searchConfigFile;
|
|
6787
7108
|
}
|
|
6788
7109
|
buildCheckPlan ??= await resolveBuildCheckPlan(ctx, repoRoot, input);
|
|
6789
|
-
const searchFilesChanged = [
|
|
6790
|
-
...new Set(useWizard.getState().writtenFiles)
|
|
6791
|
-
].map((file) => relative7(repoRoot, file));
|
|
6792
7110
|
const validation = await runValidationUseCase(
|
|
6793
|
-
validationRunInstructions(buildCheckPlan,
|
|
7111
|
+
validationRunInstructions(buildCheckPlan, attempt)
|
|
6794
7112
|
);
|
|
6795
7113
|
summaries.push(formatSummary("validation", validation.summary));
|
|
6796
7114
|
const finalAttempt = attempt === MAX_IMPLEMENT_VALIDATION_ATTEMPTS;
|
|
@@ -6824,8 +7142,16 @@ ${detail}` : ""}`
|
|
|
6824
7142
|
if (finalAttempt) {
|
|
6825
7143
|
ctx.setUserInput("implementation", "fail");
|
|
6826
7144
|
trackComponentGenerated(false, attempt);
|
|
7145
|
+
logger.warn(
|
|
7146
|
+
{
|
|
7147
|
+
summary: validation.summary,
|
|
7148
|
+
additionalInstructions: validation.additionalInstructions,
|
|
7149
|
+
unrelatedFailure: validation.unrelatedFailure
|
|
7150
|
+
},
|
|
7151
|
+
"implement: search validation failed on the final attempt"
|
|
7152
|
+
);
|
|
6827
7153
|
throw new Error(
|
|
6828
|
-
`Unable to validate the implementation after ${MAX_IMPLEMENT_VALIDATION_ATTEMPTS} attempts
|
|
7154
|
+
`Unable to validate the implementation after ${MAX_IMPLEMENT_VALIDATION_ATTEMPTS} attempts (details in wizard.log): ${briefSummary(validation.summary)}`
|
|
6829
7155
|
);
|
|
6830
7156
|
}
|
|
6831
7157
|
extraInstructions = validation.sufficient ? [] : validationRetryInstructions(validation);
|
|
@@ -6911,8 +7237,8 @@ var defaultWorkflow = {
|
|
|
6911
7237
|
defineStep({
|
|
6912
7238
|
id: "select-index",
|
|
6913
7239
|
title: "Set up index",
|
|
6914
|
-
outputSchema:
|
|
6915
|
-
selection:
|
|
7240
|
+
outputSchema: z30.object({
|
|
7241
|
+
selection: z30.string()
|
|
6916
7242
|
}),
|
|
6917
7243
|
run: (ctx) => selectIndexStep(ctx)
|
|
6918
7244
|
}),
|
|
@@ -6973,7 +7299,7 @@ var defaultWorkflow = {
|
|
|
6973
7299
|
import "zod";
|
|
6974
7300
|
|
|
6975
7301
|
// src/actions/playbookSteps/selectPlaybook.ts
|
|
6976
|
-
import { z as
|
|
7302
|
+
import { z as z33 } from "zod";
|
|
6977
7303
|
|
|
6978
7304
|
// src/lib/dashboardApi/client.ts
|
|
6979
7305
|
import "zod";
|
|
@@ -7032,67 +7358,67 @@ async function dashboardError(res) {
|
|
|
7032
7358
|
}
|
|
7033
7359
|
|
|
7034
7360
|
// src/lib/dashboardApi/schemas.ts
|
|
7035
|
-
import { z as
|
|
7036
|
-
var messageResponseSchema =
|
|
7037
|
-
message:
|
|
7361
|
+
import { z as z32 } from "zod";
|
|
7362
|
+
var messageResponseSchema = z32.object({
|
|
7363
|
+
message: z32.string()
|
|
7038
7364
|
});
|
|
7039
|
-
var playbookSchema =
|
|
7040
|
-
slug:
|
|
7041
|
-
category:
|
|
7042
|
-
title:
|
|
7043
|
-
description:
|
|
7365
|
+
var playbookSchema = z32.object({
|
|
7366
|
+
slug: z32.string(),
|
|
7367
|
+
category: z32.string().optional(),
|
|
7368
|
+
title: z32.string(),
|
|
7369
|
+
description: z32.string().optional()
|
|
7044
7370
|
});
|
|
7045
|
-
var playbooksListSchema =
|
|
7046
|
-
var playbookExecutionStepSchema =
|
|
7371
|
+
var playbooksListSchema = z32.array(playbookSchema);
|
|
7372
|
+
var playbookExecutionStepSchema = z32.enum([
|
|
7047
7373
|
"assessment",
|
|
7048
7374
|
"optimization",
|
|
7049
7375
|
"implement"
|
|
7050
7376
|
]);
|
|
7051
|
-
var playbookExecutionStepStatusSchema =
|
|
7377
|
+
var playbookExecutionStepStatusSchema = z32.enum([
|
|
7052
7378
|
"not_started",
|
|
7053
7379
|
"in_progress",
|
|
7054
7380
|
"done",
|
|
7055
7381
|
"error"
|
|
7056
7382
|
]);
|
|
7057
|
-
var schemaComparisonEntrySchema =
|
|
7058
|
-
canonical_attr:
|
|
7059
|
-
canonical_type:
|
|
7060
|
-
canonical_roles:
|
|
7061
|
-
canonical_required:
|
|
7062
|
-
index_attr:
|
|
7063
|
-
index_type:
|
|
7064
|
-
index_required:
|
|
7065
|
-
status:
|
|
7066
|
-
mapping_confidence:
|
|
7383
|
+
var schemaComparisonEntrySchema = z32.object({
|
|
7384
|
+
canonical_attr: z32.string(),
|
|
7385
|
+
canonical_type: z32.array(z32.string()),
|
|
7386
|
+
canonical_roles: z32.array(z32.string()),
|
|
7387
|
+
canonical_required: z32.boolean(),
|
|
7388
|
+
index_attr: z32.string().nullable(),
|
|
7389
|
+
index_type: z32.array(z32.string()).nullable(),
|
|
7390
|
+
index_required: z32.boolean().nullable(),
|
|
7391
|
+
status: z32.string(),
|
|
7392
|
+
mapping_confidence: z32.number()
|
|
7067
7393
|
});
|
|
7068
|
-
var settingsRecommendationSchema =
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
current:
|
|
7072
|
-
recommended:
|
|
7394
|
+
var settingsRecommendationSchema = z32.record(
|
|
7395
|
+
z32.string(),
|
|
7396
|
+
z32.object({
|
|
7397
|
+
current: z32.any(),
|
|
7398
|
+
recommended: z32.any()
|
|
7073
7399
|
})
|
|
7074
7400
|
);
|
|
7075
|
-
var assessmentResultSchema =
|
|
7076
|
-
ready:
|
|
7077
|
-
schema_comparison:
|
|
7401
|
+
var assessmentResultSchema = z32.object({
|
|
7402
|
+
ready: z32.boolean(),
|
|
7403
|
+
schema_comparison: z32.array(schemaComparisonEntrySchema),
|
|
7078
7404
|
settings_recommendation: settingsRecommendationSchema
|
|
7079
7405
|
});
|
|
7080
|
-
var playbookExecutionSchema =
|
|
7081
|
-
uuid:
|
|
7082
|
-
playbook_slug:
|
|
7083
|
-
application_id:
|
|
7084
|
-
source_index_name:
|
|
7406
|
+
var playbookExecutionSchema = z32.object({
|
|
7407
|
+
uuid: z32.string(),
|
|
7408
|
+
playbook_slug: z32.string(),
|
|
7409
|
+
application_id: z32.string(),
|
|
7410
|
+
source_index_name: z32.string(),
|
|
7085
7411
|
current_step: playbookExecutionStepSchema,
|
|
7086
7412
|
step_status: playbookExecutionStepStatusSchema,
|
|
7087
|
-
status_reason:
|
|
7088
|
-
completed:
|
|
7413
|
+
status_reason: z32.string().nullable(),
|
|
7414
|
+
completed: z32.boolean(),
|
|
7089
7415
|
assessment_result: assessmentResultSchema.nullable(),
|
|
7090
|
-
settings_snapshot:
|
|
7091
|
-
created_at:
|
|
7416
|
+
settings_snapshot: z32.json().nullable(),
|
|
7417
|
+
created_at: z32.iso.datetime()
|
|
7092
7418
|
});
|
|
7093
7419
|
var playbookExecutionCreateResponseSchema = playbookExecutionSchema.extend({
|
|
7094
|
-
resumed:
|
|
7095
|
-
sample_data:
|
|
7420
|
+
resumed: z32.boolean(),
|
|
7421
|
+
sample_data: z32.boolean()
|
|
7096
7422
|
});
|
|
7097
7423
|
|
|
7098
7424
|
// src/lib/dashboardApi/api.ts
|
|
@@ -7142,8 +7468,8 @@ async function assessPlaybookExecution(uuid) {
|
|
|
7142
7468
|
}
|
|
7143
7469
|
|
|
7144
7470
|
// src/actions/playbookSteps/selectPlaybook.ts
|
|
7145
|
-
var selectPlaybookSchema =
|
|
7146
|
-
slug:
|
|
7471
|
+
var selectPlaybookSchema = z33.object({
|
|
7472
|
+
slug: z33.string()
|
|
7147
7473
|
});
|
|
7148
7474
|
function persistSlug(ctx, slug) {
|
|
7149
7475
|
ctx.setUserInput("playbookSlug", slug);
|
|
@@ -7175,10 +7501,10 @@ async function selectPlaybookStep(ctx, requestedSlug) {
|
|
|
7175
7501
|
}
|
|
7176
7502
|
|
|
7177
7503
|
// src/actions/playbookSteps/selectIndex.ts
|
|
7178
|
-
import { z as
|
|
7179
|
-
var selectIndexSchema =
|
|
7180
|
-
indexName:
|
|
7181
|
-
sampleIndex:
|
|
7504
|
+
import { z as z34 } from "zod";
|
|
7505
|
+
var selectIndexSchema = z34.object({
|
|
7506
|
+
indexName: z34.string(),
|
|
7507
|
+
sampleIndex: z34.boolean()
|
|
7182
7508
|
});
|
|
7183
7509
|
var CREATE_SAMPLE_INDEX = "Create a sample index";
|
|
7184
7510
|
var GO_BACK_OPTION = "Go back to playbook selection";
|
|
@@ -8736,7 +9062,7 @@ function App() {
|
|
|
8736
9062
|
onComplete: () => setTipsComplete(true)
|
|
8737
9063
|
}
|
|
8738
9064
|
),
|
|
8739
|
-
showNoticesInMain && showNotices && /* @__PURE__ */ jsx21(Notices, {
|
|
9065
|
+
showNoticesInMain && showNotices && /* @__PURE__ */ jsx21(Notices, { border: false }),
|
|
8740
9066
|
/* @__PURE__ */ jsx21(Review, {}),
|
|
8741
9067
|
!showTips && /* @__PURE__ */ jsx21(PromptInput, {}),
|
|
8742
9068
|
phase === "error" && error && /* @__PURE__ */ jsx21(Box22, { marginTop: 1, children: /* @__PURE__ */ jsxs20(Text23, { color: COLORS.status.error, children: [
|
|
@@ -8967,7 +9293,7 @@ async function seedWorkflowState(workflow, value) {
|
|
|
8967
9293
|
}
|
|
8968
9294
|
|
|
8969
9295
|
// src/lib/resetState.ts
|
|
8970
|
-
import { readdir as
|
|
9296
|
+
import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
8971
9297
|
import { join as join12 } from "node:path";
|
|
8972
9298
|
var KEEP = ["wizard.log"];
|
|
8973
9299
|
async function resetProjectState() {
|
|
@@ -8975,7 +9301,7 @@ async function resetProjectState() {
|
|
|
8975
9301
|
await forgetResolvedKeys();
|
|
8976
9302
|
let entries;
|
|
8977
9303
|
try {
|
|
8978
|
-
entries = await
|
|
9304
|
+
entries = await readdir4(dir);
|
|
8979
9305
|
} catch {
|
|
8980
9306
|
return { dir, removed: [] };
|
|
8981
9307
|
}
|
|
@@ -8991,7 +9317,7 @@ async function resetProjectState() {
|
|
|
8991
9317
|
// package.json with { type: 'json' }
|
|
8992
9318
|
var package_default2 = {
|
|
8993
9319
|
name: "@algolia/wizard",
|
|
8994
|
-
version: "0.
|
|
9320
|
+
version: "0.84.0-rc.202.645",
|
|
8995
9321
|
description: "Magically implement Algolia functionality in your codebase",
|
|
8996
9322
|
type: "module",
|
|
8997
9323
|
engines: {
|