@algolia/wizard 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +252 -447
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1093,9 +1093,14 @@ var sidebarItems = [
|
|
|
1093
1093
|
];
|
|
1094
1094
|
|
|
1095
1095
|
// src/ui/Welcome.tsx
|
|
1096
|
-
import Image, {
|
|
1096
|
+
import Image, { TerminalInfoContext, defaultTerminalInfo } from "ink-picture";
|
|
1097
1097
|
import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1098
1098
|
var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
|
|
1099
|
+
var TERMINAL_INFO = {
|
|
1100
|
+
...defaultTerminalInfo,
|
|
1101
|
+
supportsUnicode: true,
|
|
1102
|
+
supportsColor: true
|
|
1103
|
+
};
|
|
1099
1104
|
function SidebarItem({
|
|
1100
1105
|
title,
|
|
1101
1106
|
description
|
|
@@ -1142,7 +1147,7 @@ function Welcome() {
|
|
|
1142
1147
|
flexDirection: "column",
|
|
1143
1148
|
justifyContent: "center",
|
|
1144
1149
|
children: /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", gap: 2, children: [
|
|
1145
|
-
/* @__PURE__ */ jsx7(
|
|
1150
|
+
/* @__PURE__ */ jsx7(TerminalInfoContext.Provider, { value: TERMINAL_INFO, children: /* @__PURE__ */ jsx7(
|
|
1146
1151
|
Image,
|
|
1147
1152
|
{
|
|
1148
1153
|
src: IMAGE_PATH,
|
|
@@ -2241,7 +2246,7 @@ async function ensureApplication() {
|
|
|
2241
2246
|
}
|
|
2242
2247
|
|
|
2243
2248
|
// src/workflows/default.ts
|
|
2244
|
-
import { z as
|
|
2249
|
+
import { z as z28 } from "zod";
|
|
2245
2250
|
|
|
2246
2251
|
// src/actions/listIndices.ts
|
|
2247
2252
|
import { z as z5 } from "zod";
|
|
@@ -2874,102 +2879,13 @@ function searchFilesTool(ctx) {
|
|
|
2874
2879
|
});
|
|
2875
2880
|
}
|
|
2876
2881
|
|
|
2877
|
-
// src/lib/tools/
|
|
2882
|
+
// src/lib/tools/runShell.ts
|
|
2878
2883
|
import { tool as tool8 } from "ai";
|
|
2879
2884
|
import z15 from "zod";
|
|
2880
|
-
|
|
2881
|
-
// src/lib/tools/utils/runCommand.ts
|
|
2882
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
2883
|
-
function runCommand(command, args, cwd) {
|
|
2884
|
-
return new Promise((resolve4) => {
|
|
2885
|
-
let output = "";
|
|
2886
|
-
const child = spawn2(command, args, {
|
|
2887
|
-
cwd,
|
|
2888
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2889
|
-
});
|
|
2890
|
-
child.stdout?.on("data", (d) => output += d);
|
|
2891
|
-
child.stderr?.on("data", (d) => output += d);
|
|
2892
|
-
child.on(
|
|
2893
|
-
"error",
|
|
2894
|
-
(err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
|
|
2895
|
-
);
|
|
2896
|
-
child.on("close", (code) => resolve4({ code: code ?? 1, output }));
|
|
2897
|
-
});
|
|
2898
|
-
}
|
|
2899
|
-
|
|
2900
|
-
// src/lib/tools/utils/packageManager.ts
|
|
2901
|
-
import { readFile as readFile7 } from "node:fs/promises";
|
|
2902
|
-
import { existsSync } from "node:fs";
|
|
2903
|
-
import { join as join9 } from "node:path";
|
|
2904
|
-
var LOCKFILES = [
|
|
2905
|
-
["pnpm-lock.yaml", "pnpm"],
|
|
2906
|
-
["yarn.lock", "yarn"],
|
|
2907
|
-
["bun.lockb", "bun"],
|
|
2908
|
-
["bun.lock", "bun"],
|
|
2909
|
-
["package-lock.json", "npm"]
|
|
2910
|
-
];
|
|
2911
|
-
async function readPackageJson(cwd = process.cwd()) {
|
|
2912
|
-
return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
|
|
2913
|
-
}
|
|
2914
|
-
function packageManagerFrom(pkg) {
|
|
2915
|
-
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2916
|
-
}
|
|
2917
|
-
function packageManagerFromLockfile(cwd) {
|
|
2918
|
-
return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
|
|
2919
|
-
}
|
|
2920
|
-
async function detectPackageManager(cwd) {
|
|
2921
|
-
try {
|
|
2922
|
-
const pkg = await readPackageJson(cwd);
|
|
2923
|
-
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2924
|
-
} catch {
|
|
2925
|
-
}
|
|
2926
|
-
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2927
|
-
}
|
|
2928
|
-
|
|
2929
|
-
// src/lib/tools/repoVerification.ts
|
|
2930
|
-
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
2931
|
-
async function runRepoVerificationCheck() {
|
|
2932
|
-
let pkg;
|
|
2933
|
-
try {
|
|
2934
|
-
pkg = await readPackageJson();
|
|
2935
|
-
} catch (err) {
|
|
2936
|
-
const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
|
|
2937
|
-
return { ok: false, checks: [], limitation };
|
|
2938
|
-
}
|
|
2939
|
-
const scripts = pkg.scripts ?? {};
|
|
2940
|
-
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
2941
|
-
if (present.length === 0) {
|
|
2942
|
-
const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
|
|
2943
|
-
return { ok: false, checks: [], limitation };
|
|
2944
|
-
}
|
|
2945
|
-
const pm = await detectPackageManager(process.cwd());
|
|
2946
|
-
const checks = [];
|
|
2947
|
-
for (const script of present) {
|
|
2948
|
-
const command = `${pm} run ${script}`;
|
|
2949
|
-
const { code, output } = await runCommand(pm, ["run", script]);
|
|
2950
|
-
checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
|
|
2951
|
-
}
|
|
2952
|
-
return { ok: checks.every((c) => c.ok), checks };
|
|
2953
|
-
}
|
|
2954
|
-
|
|
2955
|
-
// src/lib/tools/verifyImplementation.ts
|
|
2956
|
-
function verifyImplementationTool() {
|
|
2957
|
-
return tool8({
|
|
2958
|
-
description: "Run the repo's mechanical verification check for generated implementation changes. Detects lint/typecheck/check from package.json and returns structured pass/fail evidence for the verifier to interpret.",
|
|
2959
|
-
inputSchema: z15.object(),
|
|
2960
|
-
execute: async () => {
|
|
2961
|
-
logger.info("called verifyImplementation tool");
|
|
2962
|
-
return runRepoVerificationCheck();
|
|
2963
|
-
}
|
|
2964
|
-
});
|
|
2965
|
-
}
|
|
2966
|
-
|
|
2967
|
-
// src/lib/tools/runShell.ts
|
|
2968
|
-
import { tool as tool9 } from "ai";
|
|
2969
|
-
import z16 from "zod";
|
|
2885
|
+
import { relative as relative2 } from "node:path";
|
|
2970
2886
|
|
|
2971
2887
|
// src/lib/tools/utils/runShell.ts
|
|
2972
|
-
import { spawn as
|
|
2888
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
2973
2889
|
|
|
2974
2890
|
// src/lib/tools/context.ts
|
|
2975
2891
|
var DEFAULT_TOOL_LIMITS = {
|
|
@@ -3024,7 +2940,7 @@ function runShell(command, opts) {
|
|
|
3024
2940
|
let output = "";
|
|
3025
2941
|
let timedOut = false;
|
|
3026
2942
|
let settled = false;
|
|
3027
|
-
const child =
|
|
2943
|
+
const child = spawn2(command, {
|
|
3028
2944
|
shell: true,
|
|
3029
2945
|
cwd: opts.cwd,
|
|
3030
2946
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -3061,6 +2977,21 @@ function runShell(command, opts) {
|
|
|
3061
2977
|
}
|
|
3062
2978
|
|
|
3063
2979
|
// src/lib/tools/runShell.ts
|
|
2980
|
+
function storeApproval(root) {
|
|
2981
|
+
return async (req) => {
|
|
2982
|
+
const rel = relative2(root, req.cwd);
|
|
2983
|
+
const answer = await useWizard.getState().requestUserInput({
|
|
2984
|
+
prompt: "Run this command?",
|
|
2985
|
+
promptType: "commandApproval",
|
|
2986
|
+
options: [],
|
|
2987
|
+
command: {
|
|
2988
|
+
...req,
|
|
2989
|
+
cwd: rel === "" || rel.startsWith("..") ? req.cwd : rel
|
|
2990
|
+
}
|
|
2991
|
+
});
|
|
2992
|
+
return answer === "approve" ? "approve" : "reject";
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
3064
2995
|
var shellChain = Promise.resolve();
|
|
3065
2996
|
function serialize(work) {
|
|
3066
2997
|
const result = shellChain.then(work);
|
|
@@ -3111,16 +3042,16 @@ async function approveAndRun(ctx, command, cwd, explanation) {
|
|
|
3111
3042
|
};
|
|
3112
3043
|
}
|
|
3113
3044
|
function runShellTool(ctx) {
|
|
3114
|
-
return
|
|
3045
|
+
return tool8({
|
|
3115
3046
|
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. The user sees and approves every command before it runs, so write a clear `explanation`. If the user rejects a command, do not retry it \u2014 propose a different approach.",
|
|
3116
|
-
inputSchema:
|
|
3117
|
-
command:
|
|
3047
|
+
inputSchema: z15.object({
|
|
3048
|
+
command: z15.string().describe(
|
|
3118
3049
|
"The command to run, exactly as it would be typed in a shell. Pipes, && and redirects are allowed."
|
|
3119
3050
|
),
|
|
3120
|
-
cwd:
|
|
3051
|
+
cwd: z15.string().optional().describe(
|
|
3121
3052
|
"Directory to run in, relative to the project root. Defaults to the project root."
|
|
3122
3053
|
),
|
|
3123
|
-
explanation:
|
|
3054
|
+
explanation: z15.string().describe(
|
|
3124
3055
|
"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."
|
|
3125
3056
|
)
|
|
3126
3057
|
}),
|
|
@@ -3139,12 +3070,12 @@ function runShellTool(ctx) {
|
|
|
3139
3070
|
}
|
|
3140
3071
|
|
|
3141
3072
|
// src/lib/tools/generateRecord.ts
|
|
3142
|
-
import { tool as
|
|
3073
|
+
import { tool as tool9, generateText, Output, NoObjectGeneratedError } from "ai";
|
|
3143
3074
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
3144
3075
|
import { nanoid as nanoid2 } from "nanoid";
|
|
3145
3076
|
import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
|
|
3146
3077
|
import { dirname as dirname6 } from "node:path";
|
|
3147
|
-
import
|
|
3078
|
+
import z16 from "zod";
|
|
3148
3079
|
var DATA_DIR = ".algolia-wizard/data";
|
|
3149
3080
|
var RECORD_MODEL = "claude-haiku-4-5";
|
|
3150
3081
|
var MAX_RECORDS = 100;
|
|
@@ -3154,19 +3085,19 @@ var anthropic = createAnthropic({
|
|
|
3154
3085
|
apiKey: process.env.PROVIDER_API_KEY ?? ""
|
|
3155
3086
|
});
|
|
3156
3087
|
function generateRecordTool(ctx) {
|
|
3157
|
-
return
|
|
3088
|
+
return tool9({
|
|
3158
3089
|
description: "Generate realistic sample records for an entity and write them to a JSON file in the worktree. 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.",
|
|
3159
|
-
inputSchema:
|
|
3160
|
-
entityName:
|
|
3161
|
-
attributes:
|
|
3162
|
-
count:
|
|
3163
|
-
hint:
|
|
3090
|
+
inputSchema: z16.object({
|
|
3091
|
+
entityName: z16.string().describe("Name of the entity to generate records for."),
|
|
3092
|
+
attributes: z16.array(z16.string()).describe("Attribute names each record must contain."),
|
|
3093
|
+
count: z16.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
|
|
3094
|
+
hint: z16.string().optional().describe("Optional context to steer realistic values.")
|
|
3164
3095
|
}),
|
|
3165
3096
|
execute: async ({ entityName, attributes, count, hint }) => {
|
|
3166
3097
|
logger.info({ entityName, count }, "called generateRecord tool");
|
|
3167
3098
|
try {
|
|
3168
|
-
const value =
|
|
3169
|
-
const recordSchema =
|
|
3099
|
+
const value = z16.union([z16.string(), z16.number(), z16.boolean(), z16.null()]);
|
|
3100
|
+
const recordSchema = z16.object(
|
|
3170
3101
|
Object.fromEntries(attributes.map((attr) => [attr, value]))
|
|
3171
3102
|
);
|
|
3172
3103
|
const generateBatch = async (batchCount) => {
|
|
@@ -3176,8 +3107,8 @@ function generateRecordTool(ctx) {
|
|
|
3176
3107
|
const { output } = await generateText({
|
|
3177
3108
|
model: anthropic(RECORD_MODEL),
|
|
3178
3109
|
output: Output.object({
|
|
3179
|
-
schema:
|
|
3180
|
-
records:
|
|
3110
|
+
schema: z16.object({
|
|
3111
|
+
records: z16.array(recordSchema).length(batchCount)
|
|
3181
3112
|
})
|
|
3182
3113
|
}),
|
|
3183
3114
|
prompt: [
|
|
@@ -3234,13 +3165,13 @@ function generateRecordTool(ctx) {
|
|
|
3234
3165
|
}
|
|
3235
3166
|
|
|
3236
3167
|
// src/lib/tools/notifyUser.ts
|
|
3237
|
-
import { tool as
|
|
3238
|
-
import
|
|
3168
|
+
import { tool as tool10 } from "ai";
|
|
3169
|
+
import z17 from "zod";
|
|
3239
3170
|
function notifyUserTool() {
|
|
3240
|
-
return
|
|
3171
|
+
return tool10({
|
|
3241
3172
|
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.`,
|
|
3242
|
-
inputSchema:
|
|
3243
|
-
message:
|
|
3173
|
+
inputSchema: z17.object({
|
|
3174
|
+
message: z17.string().describe(
|
|
3244
3175
|
"Short, plain-language description of what you are doing now."
|
|
3245
3176
|
)
|
|
3246
3177
|
}),
|
|
@@ -3283,10 +3214,6 @@ function createTools(ctx, { output, tools }) {
|
|
|
3283
3214
|
writeCredentialsTool(ctx)
|
|
3284
3215
|
),
|
|
3285
3216
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
3286
|
-
verifyImplementation: withLogging(
|
|
3287
|
-
"verifyImplementation",
|
|
3288
|
-
verifyImplementationTool()
|
|
3289
|
-
),
|
|
3290
3217
|
runShell: withLogging("runShell", runShellTool(ctx)),
|
|
3291
3218
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
3292
3219
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
@@ -3409,10 +3336,10 @@ async function runAgent(req) {
|
|
|
3409
3336
|
}
|
|
3410
3337
|
|
|
3411
3338
|
// src/actions/detectLanguage.ts
|
|
3412
|
-
import
|
|
3413
|
-
var detectLanguageSchema =
|
|
3414
|
-
languages:
|
|
3415
|
-
frameworks:
|
|
3339
|
+
import z20 from "zod";
|
|
3340
|
+
var detectLanguageSchema = z20.object({
|
|
3341
|
+
languages: z20.array(z20.object({ name: z20.string(), version: z20.string() })),
|
|
3342
|
+
frameworks: z20.array(z20.object({ name: z20.string(), version: z20.string() }))
|
|
3416
3343
|
});
|
|
3417
3344
|
var detectLanguage = () => runAgent({
|
|
3418
3345
|
instructions: [
|
|
@@ -3430,31 +3357,31 @@ var detectLanguage = () => runAgent({
|
|
|
3430
3357
|
});
|
|
3431
3358
|
|
|
3432
3359
|
// src/actions/analyzeCodebase.ts
|
|
3433
|
-
import
|
|
3360
|
+
import z21 from "zod";
|
|
3434
3361
|
var READONLY_TOOLS = [
|
|
3435
3362
|
"listFiles",
|
|
3436
3363
|
"changeDirectory",
|
|
3437
3364
|
"readFile",
|
|
3438
3365
|
"searchFiles"
|
|
3439
3366
|
];
|
|
3440
|
-
var ingestionAnalysisSchema =
|
|
3441
|
-
ingestionAnalysis:
|
|
3442
|
-
|
|
3443
|
-
name:
|
|
3444
|
-
paths:
|
|
3367
|
+
var ingestionAnalysisSchema = z21.object({
|
|
3368
|
+
ingestionAnalysis: z21.array(
|
|
3369
|
+
z21.object({
|
|
3370
|
+
name: z21.string(),
|
|
3371
|
+
paths: z21.array(z21.string()),
|
|
3445
3372
|
// indexable fields the agent found for this entity
|
|
3446
|
-
attributes:
|
|
3373
|
+
attributes: z21.array(z21.string())
|
|
3447
3374
|
})
|
|
3448
3375
|
)
|
|
3449
3376
|
});
|
|
3450
|
-
var searchImplementationAnalysisSchema =
|
|
3451
|
-
searchImplementationAnalysis:
|
|
3377
|
+
var searchImplementationAnalysisSchema = z21.object({
|
|
3378
|
+
searchImplementationAnalysis: z21.string()
|
|
3452
3379
|
});
|
|
3453
|
-
var verificationSchema =
|
|
3454
|
-
verification:
|
|
3380
|
+
var verificationSchema = z21.object({
|
|
3381
|
+
verification: z21.array(z21.string())
|
|
3455
3382
|
});
|
|
3456
3383
|
var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
|
|
3457
|
-
var analyzeCodebaseSchema =
|
|
3384
|
+
var analyzeCodebaseSchema = z21.object({
|
|
3458
3385
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
3459
3386
|
searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
|
|
3460
3387
|
verification: verificationSchema.shape.verification.optional(),
|
|
@@ -3516,7 +3443,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3516
3443
|
// package.json
|
|
3517
3444
|
var package_default = {
|
|
3518
3445
|
name: "@algolia/wizard",
|
|
3519
|
-
version: "0.
|
|
3446
|
+
version: "0.13.0",
|
|
3520
3447
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3521
3448
|
type: "module",
|
|
3522
3449
|
engines: {
|
|
@@ -3635,8 +3562,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
|
|
|
3635
3562
|
}
|
|
3636
3563
|
|
|
3637
3564
|
// src/actions/confirmLanguage.ts
|
|
3638
|
-
import
|
|
3639
|
-
var confirmLanguageSchema =
|
|
3565
|
+
import z23 from "zod";
|
|
3566
|
+
var confirmLanguageSchema = z23.object({
|
|
3640
3567
|
languages: detectLanguageSchema.shape.languages
|
|
3641
3568
|
});
|
|
3642
3569
|
async function confirmLanguage(ctx) {
|
|
@@ -3657,8 +3584,8 @@ async function confirmLanguage(ctx) {
|
|
|
3657
3584
|
}
|
|
3658
3585
|
|
|
3659
3586
|
// src/actions/confirmFramework.ts
|
|
3660
|
-
import
|
|
3661
|
-
var confirmFrameworkSchema =
|
|
3587
|
+
import z24 from "zod";
|
|
3588
|
+
var confirmFrameworkSchema = z24.object({
|
|
3662
3589
|
frameworks: detectLanguageSchema.shape.frameworks
|
|
3663
3590
|
});
|
|
3664
3591
|
var CURATED_FRAMEWORKS = [
|
|
@@ -3786,8 +3713,8 @@ async function promptUser(ctx, params) {
|
|
|
3786
3713
|
}
|
|
3787
3714
|
|
|
3788
3715
|
// src/actions/confirmEntities.ts
|
|
3789
|
-
import
|
|
3790
|
-
var confirmEntitiesSchema =
|
|
3716
|
+
import z25 from "zod";
|
|
3717
|
+
var confirmEntitiesSchema = z25.object({
|
|
3791
3718
|
// Final detection — the focused re-run may supersede project-scan's.
|
|
3792
3719
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
3793
3720
|
confirmedEntities: confirmedEntitiesFieldSchema
|
|
@@ -3857,15 +3784,15 @@ async function confirmEntities(ctx) {
|
|
|
3857
3784
|
}
|
|
3858
3785
|
|
|
3859
3786
|
// src/actions/review.ts
|
|
3860
|
-
import { z as
|
|
3861
|
-
var reviewSchema =
|
|
3787
|
+
import { z as z26 } from "zod";
|
|
3788
|
+
var reviewSchema = z26.object({
|
|
3862
3789
|
// Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
|
|
3863
3790
|
// not one entry per workflow step — a step's raw output can be a long,
|
|
3864
3791
|
// multi-paragraph blob (see implement.ts's summaries.join), and mirroring
|
|
3865
3792
|
// that 1:1 is what made the old per-step summary an unreadable wall of text.
|
|
3866
|
-
summaryPoints:
|
|
3867
|
-
reviewPrompt:
|
|
3868
|
-
nextSteps:
|
|
3793
|
+
summaryPoints: z26.array(z26.string()),
|
|
3794
|
+
reviewPrompt: z26.string(),
|
|
3795
|
+
nextSteps: z26.array(z26.string())
|
|
3869
3796
|
});
|
|
3870
3797
|
function formatCompletedSteps(steps) {
|
|
3871
3798
|
if (!steps.length) return "(no prior steps completed)";
|
|
@@ -3916,19 +3843,12 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3916
3843
|
};
|
|
3917
3844
|
|
|
3918
3845
|
// src/actions/implement.ts
|
|
3919
|
-
import
|
|
3846
|
+
import z27 from "zod";
|
|
3920
3847
|
|
|
3921
3848
|
// src/lib/worktree.ts
|
|
3922
|
-
import { execFile
|
|
3923
|
-
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as
|
|
3924
|
-
import {
|
|
3925
|
-
basename as basename2,
|
|
3926
|
-
dirname as dirname7,
|
|
3927
|
-
isAbsolute as isAbsolute2,
|
|
3928
|
-
join as join10,
|
|
3929
|
-
relative as relative2,
|
|
3930
|
-
resolve as resolve3
|
|
3931
|
-
} from "node:path";
|
|
3849
|
+
import { execFile } from "node:child_process";
|
|
3850
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3851
|
+
import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join9, resolve as resolve3 } from "node:path";
|
|
3932
3852
|
var MAX_BUFFER = 32 * 1024 * 1024;
|
|
3933
3853
|
var MAX_WIZARD_WORKTREES = 3;
|
|
3934
3854
|
var WIZARD_BRANCH_PREFIX = "wizard/implement-";
|
|
@@ -3959,7 +3879,7 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
3959
3879
|
return out.trim().length > 0;
|
|
3960
3880
|
}
|
|
3961
3881
|
async function pruneOldWorktrees(repoRoot) {
|
|
3962
|
-
const dir =
|
|
3882
|
+
const dir = join9(stateDir(repoRoot), "worktrees");
|
|
3963
3883
|
const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
3964
3884
|
for (const slug of stale) {
|
|
3965
3885
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
@@ -3970,7 +3890,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3970
3890
|
"worktree",
|
|
3971
3891
|
"remove",
|
|
3972
3892
|
"--force",
|
|
3973
|
-
|
|
3893
|
+
join9(dir, slug)
|
|
3974
3894
|
]);
|
|
3975
3895
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
3976
3896
|
} catch (err) {
|
|
@@ -3984,113 +3904,13 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3984
3904
|
async function createWorktree(repoRoot) {
|
|
3985
3905
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
3986
3906
|
const dirSlug = branch.replace(/\//g, "-");
|
|
3987
|
-
const path =
|
|
3907
|
+
const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
|
|
3988
3908
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
3989
3909
|
await pruneOldWorktrees(repoRoot);
|
|
3990
3910
|
await mkdir6(dirname7(path), { recursive: true });
|
|
3991
3911
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
3992
3912
|
return { path, branch };
|
|
3993
3913
|
}
|
|
3994
|
-
async function installWorktreeDeps(worktreePath) {
|
|
3995
|
-
try {
|
|
3996
|
-
await readPackageJson(worktreePath);
|
|
3997
|
-
} catch {
|
|
3998
|
-
return { ok: true, output: "no package.json; skipped install" };
|
|
3999
|
-
}
|
|
4000
|
-
const pm = await detectPackageManager(worktreePath);
|
|
4001
|
-
return new Promise((resolve4) => {
|
|
4002
|
-
let output = "";
|
|
4003
|
-
const child = spawn4(pm, ["install"], {
|
|
4004
|
-
cwd: worktreePath,
|
|
4005
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
4006
|
-
});
|
|
4007
|
-
child.stdout?.on("data", (d) => output += d);
|
|
4008
|
-
child.stderr?.on("data", (d) => output += d);
|
|
4009
|
-
child.on(
|
|
4010
|
-
"error",
|
|
4011
|
-
(err) => resolve4({
|
|
4012
|
-
ok: false,
|
|
4013
|
-
output: `Failed to run ${pm} install: ${err.message}`
|
|
4014
|
-
})
|
|
4015
|
-
);
|
|
4016
|
-
child.on(
|
|
4017
|
-
"close",
|
|
4018
|
-
(code) => resolve4({ ok: code === 0, output: output.trim() })
|
|
4019
|
-
);
|
|
4020
|
-
});
|
|
4021
|
-
}
|
|
4022
|
-
var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
|
|
4023
|
-
function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
4024
|
-
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
4025
|
-
return {
|
|
4026
|
-
ok: false,
|
|
4027
|
-
reason: `entrypoint "${entrypoint}" is not a plain file path`
|
|
4028
|
-
};
|
|
4029
|
-
}
|
|
4030
|
-
const target = resolve3(worktreePath, entrypoint);
|
|
4031
|
-
const rel = relative2(worktreePath, target);
|
|
4032
|
-
if (rel.startsWith("..") || isAbsolute2(rel)) {
|
|
4033
|
-
return {
|
|
4034
|
-
ok: false,
|
|
4035
|
-
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
4036
|
-
};
|
|
4037
|
-
}
|
|
4038
|
-
return { ok: true, target };
|
|
4039
|
-
}
|
|
4040
|
-
async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
4041
|
-
if (!INGEST_RUNTIMES.includes(runtime)) {
|
|
4042
|
-
return {
|
|
4043
|
-
ran: false,
|
|
4044
|
-
ok: false,
|
|
4045
|
-
output: "",
|
|
4046
|
-
reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
|
|
4047
|
-
};
|
|
4048
|
-
}
|
|
4049
|
-
const validated = validateIngestEntrypoint(worktreePath, entrypoint);
|
|
4050
|
-
if (!validated.ok) {
|
|
4051
|
-
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
4052
|
-
}
|
|
4053
|
-
try {
|
|
4054
|
-
if (!(await stat2(validated.target)).isFile()) {
|
|
4055
|
-
return {
|
|
4056
|
-
ran: false,
|
|
4057
|
-
ok: false,
|
|
4058
|
-
output: "",
|
|
4059
|
-
reason: `entrypoint "${entrypoint}" is not a file`
|
|
4060
|
-
};
|
|
4061
|
-
}
|
|
4062
|
-
} catch {
|
|
4063
|
-
return {
|
|
4064
|
-
ran: false,
|
|
4065
|
-
ok: false,
|
|
4066
|
-
output: "",
|
|
4067
|
-
reason: `entrypoint "${entrypoint}" does not exist`
|
|
4068
|
-
};
|
|
4069
|
-
}
|
|
4070
|
-
return new Promise((resolveRun) => {
|
|
4071
|
-
let output = "";
|
|
4072
|
-
const child = spawn4(runtime, [entrypoint], {
|
|
4073
|
-
cwd: worktreePath,
|
|
4074
|
-
shell: false,
|
|
4075
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
4076
|
-
env: { ...process.env, ...env }
|
|
4077
|
-
});
|
|
4078
|
-
child.stdout?.on("data", (d) => output += d);
|
|
4079
|
-
child.stderr?.on("data", (d) => output += d);
|
|
4080
|
-
child.on(
|
|
4081
|
-
"error",
|
|
4082
|
-
(err) => resolveRun({
|
|
4083
|
-
ran: true,
|
|
4084
|
-
ok: false,
|
|
4085
|
-
output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
|
|
4086
|
-
})
|
|
4087
|
-
);
|
|
4088
|
-
child.on(
|
|
4089
|
-
"close",
|
|
4090
|
-
(code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
|
|
4091
|
-
);
|
|
4092
|
-
});
|
|
4093
|
-
}
|
|
4094
3914
|
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
4095
3915
|
const trimmed = sourcePath.trim();
|
|
4096
3916
|
if (!trimmed) {
|
|
@@ -4104,8 +3924,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
4104
3924
|
} catch {
|
|
4105
3925
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
4106
3926
|
}
|
|
4107
|
-
const relPath =
|
|
4108
|
-
const dest =
|
|
3927
|
+
const relPath = join9(ingestDir, basename2(source));
|
|
3928
|
+
const dest = join9(worktreePath, relPath);
|
|
4109
3929
|
try {
|
|
4110
3930
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
4111
3931
|
await copyFile(source, dest);
|
|
@@ -4123,7 +3943,7 @@ function hasEnvVar(content, name) {
|
|
|
4123
3943
|
async function readEnvVar(worktreePath, name) {
|
|
4124
3944
|
let content;
|
|
4125
3945
|
try {
|
|
4126
|
-
content = await
|
|
3946
|
+
content = await readFile7(join9(worktreePath, ".env"), "utf8");
|
|
4127
3947
|
} catch (err) {
|
|
4128
3948
|
if (err.code !== "ENOENT") throw err;
|
|
4129
3949
|
return void 0;
|
|
@@ -4138,10 +3958,10 @@ async function readEnvVar(worktreePath, name) {
|
|
|
4138
3958
|
return value;
|
|
4139
3959
|
}
|
|
4140
3960
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
4141
|
-
const target =
|
|
3961
|
+
const target = join9(worktreePath, ".env");
|
|
4142
3962
|
let existing = "";
|
|
4143
3963
|
try {
|
|
4144
|
-
existing = await
|
|
3964
|
+
existing = await readFile7(target, "utf8");
|
|
4145
3965
|
} catch (err) {
|
|
4146
3966
|
if (err.code !== "ENOENT") throw err;
|
|
4147
3967
|
}
|
|
@@ -4210,15 +4030,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
|
|
|
4210
4030
|
}
|
|
4211
4031
|
|
|
4212
4032
|
// src/lib/algoliaDocs.ts
|
|
4213
|
-
import { readFileSync, readdirSync, existsSync
|
|
4214
|
-
import { dirname as dirname8, join as
|
|
4033
|
+
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
4034
|
+
import { dirname as dirname8, join as join10 } from "node:path";
|
|
4215
4035
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4216
|
-
var DOCS_SUBPATH =
|
|
4036
|
+
var DOCS_SUBPATH = join10("docs", "algolia-sdk");
|
|
4217
4037
|
function findDocsDir() {
|
|
4218
4038
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
4219
4039
|
for (; ; ) {
|
|
4220
|
-
const candidate =
|
|
4221
|
-
if (
|
|
4040
|
+
const candidate = join10(dir, DOCS_SUBPATH);
|
|
4041
|
+
if (existsSync(candidate)) return candidate;
|
|
4222
4042
|
const parent = dirname8(dir);
|
|
4223
4043
|
if (parent === dir) return void 0;
|
|
4224
4044
|
dir = parent;
|
|
@@ -4240,7 +4060,7 @@ function loadAlgoliaDoc(language) {
|
|
|
4240
4060
|
);
|
|
4241
4061
|
return "";
|
|
4242
4062
|
}
|
|
4243
|
-
return readFileSync(
|
|
4063
|
+
return readFileSync(join10(docsDir, files[0]), "utf8").trim();
|
|
4244
4064
|
}
|
|
4245
4065
|
function getNamedDoc(name, language) {
|
|
4246
4066
|
const docsDir = findDocsDir();
|
|
@@ -4248,8 +4068,8 @@ function getNamedDoc(name, language) {
|
|
|
4248
4068
|
logger.warn("docs/algolia-sdk not found");
|
|
4249
4069
|
return "";
|
|
4250
4070
|
}
|
|
4251
|
-
const file =
|
|
4252
|
-
if (!
|
|
4071
|
+
const file = join10(docsDir, `${name}-${language}.md`);
|
|
4072
|
+
if (!existsSync(file)) {
|
|
4253
4073
|
logger.warn({ name, language }, "named SDK reference not found");
|
|
4254
4074
|
return "";
|
|
4255
4075
|
}
|
|
@@ -4275,34 +4095,30 @@ function shellQuote(value) {
|
|
|
4275
4095
|
}
|
|
4276
4096
|
|
|
4277
4097
|
// src/actions/implement.ts
|
|
4278
|
-
var implementSchema =
|
|
4279
|
-
filesChanged:
|
|
4280
|
-
summary:
|
|
4281
|
-
worktreePath:
|
|
4282
|
-
ingestCommand:
|
|
4283
|
-
ingestScriptRan:
|
|
4284
|
-
ingestRecordCount:
|
|
4285
|
-
ingestDurationMs:
|
|
4286
|
-
ingestionSource:
|
|
4287
|
-
searchEnvVars:
|
|
4288
|
-
|
|
4289
|
-
name:
|
|
4290
|
-
value:
|
|
4098
|
+
var implementSchema = z27.object({
|
|
4099
|
+
filesChanged: z27.array(z27.string()),
|
|
4100
|
+
summary: z27.string(),
|
|
4101
|
+
worktreePath: z27.string().optional(),
|
|
4102
|
+
ingestCommand: z27.string().optional(),
|
|
4103
|
+
ingestScriptRan: z27.boolean().optional(),
|
|
4104
|
+
ingestRecordCount: z27.number().optional(),
|
|
4105
|
+
ingestDurationMs: z27.number().optional(),
|
|
4106
|
+
ingestionSource: z27.enum(["local", "fileUpload", "generated"]),
|
|
4107
|
+
searchEnvVars: z27.array(
|
|
4108
|
+
z27.object({
|
|
4109
|
+
name: z27.string(),
|
|
4110
|
+
value: z27.string()
|
|
4291
4111
|
})
|
|
4292
4112
|
).optional()
|
|
4293
4113
|
});
|
|
4294
|
-
var implementationOutputSchema =
|
|
4295
|
-
summary:
|
|
4296
|
-
|
|
4297
|
-
// free-form command string. `runtime` is allowlisted and `entrypoint` is
|
|
4298
|
-
// validated worktree-relative, so the agent cannot inject extra commands.
|
|
4299
|
-
runtime: z28.enum(INGEST_RUNTIMES).optional(),
|
|
4300
|
-
entrypoint: z28.string().optional()
|
|
4114
|
+
var implementationOutputSchema = z27.object({
|
|
4115
|
+
summary: z27.string(),
|
|
4116
|
+
ingestCommand: z27.string().optional()
|
|
4301
4117
|
});
|
|
4302
|
-
var verificationOutputSchema =
|
|
4303
|
-
summary:
|
|
4304
|
-
sufficient:
|
|
4305
|
-
additionalInstructions:
|
|
4118
|
+
var verificationOutputSchema = z27.object({
|
|
4119
|
+
summary: z27.string(),
|
|
4120
|
+
sufficient: z27.boolean(),
|
|
4121
|
+
additionalInstructions: z27.string().optional()
|
|
4306
4122
|
});
|
|
4307
4123
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
4308
4124
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
@@ -4382,7 +4198,9 @@ function baseInstructions(input) {
|
|
|
4382
4198
|
// index-scoped keys then reject with a 403.
|
|
4383
4199
|
`Target Algolia index, to be used exactly as written \u2014 never renamed, re-cased, prefixed, or suffixed: "${input.targetIndex}"`,
|
|
4384
4200
|
`Project languages and frameworks: ${JSON.stringify(input.language)}`,
|
|
4385
|
-
"Make minimal, idiomatic changes; do not touch unrelated code."
|
|
4201
|
+
"Make minimal, idiomatic changes; do not touch unrelated code.",
|
|
4202
|
+
`Commands run through a shell on ${process.platform}. Write commands that work there.`,
|
|
4203
|
+
"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."
|
|
4386
4204
|
];
|
|
4387
4205
|
}
|
|
4388
4206
|
function sourceSpecificInstructions(input) {
|
|
@@ -4418,9 +4236,9 @@ function ingestionInstructions(input) {
|
|
|
4418
4236
|
"Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
|
|
4419
4237
|
"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.",
|
|
4420
4238
|
getNamedDoc("save-records", "js"),
|
|
4421
|
-
'
|
|
4239
|
+
'Install the Algolia client via runShell and add it to package.json "dependencies" with a valid version range, so the dependency is not just installed ad hoc.',
|
|
4240
|
+
'Then run the script yourself via runShell, and 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.',
|
|
4422
4241
|
"The summary should be extremely concise.",
|
|
4423
|
-
`Return how to run the script as two fields, not a command string: "runtime" (one of ${INGEST_RUNTIMES.join(", ")}) and "entrypoint" (the script path relative to the worktree root, e.g. "${input.ingestDir}/ingest.mjs"). The wizard runs \`<runtime> <entrypoint>\` directly, so the entrypoint must be a plain path with no flags or arguments. Write a script one of those interpreters can run as-is.`,
|
|
4424
4242
|
...sourceSpecificInstructions(input)
|
|
4425
4243
|
] : []
|
|
4426
4244
|
];
|
|
@@ -4434,6 +4252,7 @@ function searchInstructions(input) {
|
|
|
4434
4252
|
doc,
|
|
4435
4253
|
`Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app \u2014 at least a working SearchBox and Hits against the target index.`,
|
|
4436
4254
|
`Read the index name from the ${searchIndexVar(input.language)} env var, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or component name.`,
|
|
4255
|
+
"Read the App ID, the search-only API key, and the index name from env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
4437
4256
|
// The key is provisioned only after verification passes, so the agent never
|
|
4438
4257
|
// sees one. It must also leave .env alone: the wizard reads that file to
|
|
4439
4258
|
// decide whether a key already exists, and an agent-invented value there
|
|
@@ -4443,8 +4262,7 @@ function searchInstructions(input) {
|
|
|
4443
4262
|
// ".env" right after this step, so a renamed prefix would leave the code
|
|
4444
4263
|
// reading a var the wizard never wrote.
|
|
4445
4264
|
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
4446
|
-
|
|
4447
|
-
'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.',
|
|
4265
|
+
'Install any Algolia/InstantSearch packages you import via runShell, and add them to package.json "dependencies" with a valid version range.',
|
|
4448
4266
|
"The summary should be extremely concise; do not mention env var setup or manual testing steps \u2014 the wizard writes the resolved credentials to .env and reports that separately."
|
|
4449
4267
|
];
|
|
4450
4268
|
}
|
|
@@ -4452,11 +4270,12 @@ function verificationInstructions(input) {
|
|
|
4452
4270
|
return [
|
|
4453
4271
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
4454
4272
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
4455
|
-
"
|
|
4456
|
-
"
|
|
4457
|
-
"
|
|
4273
|
+
"Run the project's own checks (lint, type check, tests) via runShell, using the commands the project actually defines \u2014 its task runner, manifest scripts, or Makefile. Run every check that applies, not just the first.",
|
|
4274
|
+
"This worktree starts with no installed dependencies. If a check fails because packages or modules are missing, install the dependencies via runShell and re-run it rather than changing the code.",
|
|
4275
|
+
"For issues caused by the implementation, make minimal fixes with writeFile and re-run the checks.",
|
|
4276
|
+
"Do not make speculative fixes when no checks exist, a check cannot run, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
|
|
4458
4277
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
4459
|
-
`Do not modify "${input.ingestDir}/" unless
|
|
4278
|
+
`Do not modify "${input.ingestDir}/" unless a check reports an actionable issue in its files.`,
|
|
4460
4279
|
"Always call reportStatus with status=success once verification has run, even when sufficient=false.",
|
|
4461
4280
|
"Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
|
|
4462
4281
|
"Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
|
|
@@ -4477,14 +4296,15 @@ var IMPLEMENT_CONFIG = {
|
|
|
4477
4296
|
}
|
|
4478
4297
|
};
|
|
4479
4298
|
var useCaseToolMap = {
|
|
4480
|
-
ingestion: [
|
|
4481
|
-
search: [...FS_READ_TOOLS, "writeFile", "notifyUser"],
|
|
4482
|
-
verification: [
|
|
4299
|
+
ingestion: [
|
|
4483
4300
|
...FS_READ_TOOLS,
|
|
4484
4301
|
"writeFile",
|
|
4485
|
-
"
|
|
4302
|
+
"writeCredentials",
|
|
4303
|
+
"runShell",
|
|
4486
4304
|
"notifyUser"
|
|
4487
|
-
]
|
|
4305
|
+
],
|
|
4306
|
+
search: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"],
|
|
4307
|
+
verification: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"]
|
|
4488
4308
|
};
|
|
4489
4309
|
function toolsForUseCase(useCase, ingestionSource) {
|
|
4490
4310
|
const tools = useCaseToolMap[useCase];
|
|
@@ -4507,15 +4327,51 @@ function formatSummary(useCase, summary) {
|
|
|
4507
4327
|
const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
|
|
4508
4328
|
return `${label}: ${summary}`;
|
|
4509
4329
|
}
|
|
4510
|
-
function buildIngestCommand(worktree, runtime, entrypoint) {
|
|
4511
|
-
return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
|
|
4512
|
-
}
|
|
4513
4330
|
function parseIngestRecordCount(output) {
|
|
4514
4331
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
4515
4332
|
if (!match) return void 0;
|
|
4516
4333
|
const count = Number(match[1]);
|
|
4517
4334
|
return Number.isFinite(count) ? count : void 0;
|
|
4518
4335
|
}
|
|
4336
|
+
function ingestOutcome(executions, ingestCommand) {
|
|
4337
|
+
const newestFirst = [...executions].reverse();
|
|
4338
|
+
const withCount = newestFirst.filter(
|
|
4339
|
+
(e) => parseIngestRecordCount(e.output ?? "") != null
|
|
4340
|
+
);
|
|
4341
|
+
const succeeded = newestFirst.filter((e) => e.approved && e.exitCode === 0);
|
|
4342
|
+
return {
|
|
4343
|
+
run: succeeded.find((e) => e.command === ingestCommand) ?? succeeded.find((e) => withCount.includes(e)),
|
|
4344
|
+
attempt: ingestCommand ? newestFirst.find((e) => e.command === ingestCommand) : void 0,
|
|
4345
|
+
recordCount: parseIngestRecordCount(withCount[0]?.output ?? "")
|
|
4346
|
+
};
|
|
4347
|
+
}
|
|
4348
|
+
var INGEST_OUTPUT_TAIL_CHARS = 400;
|
|
4349
|
+
function outputTail(output) {
|
|
4350
|
+
const trimmed = output?.trim();
|
|
4351
|
+
if (!trimmed) return void 0;
|
|
4352
|
+
return trimmed.length > INGEST_OUTPUT_TAIL_CHARS ? `\u2026${trimmed.slice(-INGEST_OUTPUT_TAIL_CHARS)}` : trimmed;
|
|
4353
|
+
}
|
|
4354
|
+
function ingestFailure(attempt, executions) {
|
|
4355
|
+
if (!attempt) {
|
|
4356
|
+
return {
|
|
4357
|
+
reason: executions.some((e) => !e.approved) ? "a command it needed was declined" : "no successful run was recorded"
|
|
4358
|
+
};
|
|
4359
|
+
}
|
|
4360
|
+
if (!attempt.approved) return { reason: "you declined to run it" };
|
|
4361
|
+
const detail = outputTail(attempt.output);
|
|
4362
|
+
if (attempt.timedOut) {
|
|
4363
|
+
const seconds = Math.round((attempt.durationMs ?? 0) / 1e3);
|
|
4364
|
+
return { reason: `it timed out after ${seconds}s`, detail };
|
|
4365
|
+
}
|
|
4366
|
+
return { reason: `it failed (exit ${attempt.exitCode ?? "unknown"})`, detail };
|
|
4367
|
+
}
|
|
4368
|
+
function makeToolContext(worktree, env = async () => ({})) {
|
|
4369
|
+
return createToolContext(
|
|
4370
|
+
DEFAULT_TOOL_LIMITS,
|
|
4371
|
+
worktree,
|
|
4372
|
+
createShellContext({ env, approve: storeApproval(worktree) })
|
|
4373
|
+
);
|
|
4374
|
+
}
|
|
4519
4375
|
function verificationRetryInstructions(verification) {
|
|
4520
4376
|
return [
|
|
4521
4377
|
`Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
|
|
@@ -4594,9 +4450,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4594
4450
|
const confirmed2 = normalized.confirmedEntities;
|
|
4595
4451
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
4596
4452
|
let appId;
|
|
4453
|
+
let ingestAppId;
|
|
4597
4454
|
if (useCases.includes("search")) {
|
|
4598
4455
|
appId = (await requireApplication()).id;
|
|
4599
4456
|
}
|
|
4457
|
+
if (useCases.includes("ingestion")) {
|
|
4458
|
+
ingestAppId = appId ?? (await requireApplication()).id;
|
|
4459
|
+
}
|
|
4600
4460
|
const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
|
|
4601
4461
|
try {
|
|
4602
4462
|
process.chdir(worktree);
|
|
@@ -4654,41 +4514,31 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4654
4514
|
}
|
|
4655
4515
|
let finalSearchEnvVars = input.searchEnvVars;
|
|
4656
4516
|
let agentRuns = 0;
|
|
4657
|
-
let
|
|
4658
|
-
let ingestEntrypoint;
|
|
4517
|
+
let ingestCommand;
|
|
4659
4518
|
let ingestScriptRan = false;
|
|
4660
4519
|
let ingestRecordCount;
|
|
4661
4520
|
let ingestDurationMs;
|
|
4662
|
-
let installFailed = false;
|
|
4663
4521
|
let ingestOutcomeMessage;
|
|
4522
|
+
const ingestKeyAppId = ingestAppId;
|
|
4523
|
+
const ingestionTools = ingestKeyAppId ? makeToolContext(worktree, async () => ({
|
|
4524
|
+
[APP_ID_VAR]: ingestKeyAppId,
|
|
4525
|
+
[API_KEY_VAR]: (await resolveWriteKey(targetIndex, ingestKeyAppId)).key,
|
|
4526
|
+
[INDEX_NAME_VAR]: targetIndex
|
|
4527
|
+
})) : void 0;
|
|
4528
|
+
const searchTools = makeToolContext(worktree);
|
|
4664
4529
|
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
4665
4530
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
4666
4531
|
agentRuns += 1;
|
|
4667
|
-
|
|
4532
|
+
return runAgent({
|
|
4668
4533
|
instructions: buildAgentInstructions(
|
|
4669
4534
|
currentUseCase,
|
|
4670
4535
|
input,
|
|
4671
4536
|
extraInstructions
|
|
4672
4537
|
),
|
|
4673
4538
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
4674
|
-
outputSchema: implementationOutputSchema
|
|
4675
|
-
|
|
4676
|
-
ctx.notify({
|
|
4677
|
-
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
4678
|
-
});
|
|
4679
|
-
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
4680
|
-
useCase: currentUseCase
|
|
4539
|
+
outputSchema: implementationOutputSchema,
|
|
4540
|
+
toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
|
|
4681
4541
|
});
|
|
4682
|
-
const install = await installWorktreeDeps(worktree);
|
|
4683
|
-
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
4684
|
-
if (!install.ok) {
|
|
4685
|
-
installFailed = true;
|
|
4686
|
-
logger.warn(
|
|
4687
|
-
{ useCase: currentUseCase, output: install.output },
|
|
4688
|
-
"implement: dependency install in worktree failed; generated commands may not run until deps are installed"
|
|
4689
|
-
);
|
|
4690
|
-
}
|
|
4691
|
-
return result;
|
|
4692
4542
|
}
|
|
4693
4543
|
async function runVerificationUseCase() {
|
|
4694
4544
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
@@ -4696,111 +4546,75 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4696
4546
|
return runAgent({
|
|
4697
4547
|
instructions: buildAgentInstructions("verification", input),
|
|
4698
4548
|
tools: toolsForUseCase("verification"),
|
|
4699
|
-
outputSchema: verificationOutputSchema
|
|
4549
|
+
outputSchema: verificationOutputSchema,
|
|
4550
|
+
toolContext: searchTools
|
|
4700
4551
|
});
|
|
4701
4552
|
}
|
|
4702
4553
|
if (useCases.includes("ingestion")) {
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4554
|
+
let ingestFailureDetail;
|
|
4555
|
+
const result = await runImplementationUseCase("ingestion");
|
|
4556
|
+
summaries.push(formatSummary("ingestion", result.summary));
|
|
4557
|
+
ingestCommand = result.ingestCommand;
|
|
4558
|
+
const executions = (ingestionTools ?? searchTools).shell.executions;
|
|
4559
|
+
const {
|
|
4560
|
+
run: ingestRun,
|
|
4561
|
+
attempt: ingestAttempt,
|
|
4562
|
+
recordCount
|
|
4563
|
+
} = ingestOutcome(executions, ingestCommand);
|
|
4564
|
+
ingestScriptRan = ingestRun != null;
|
|
4565
|
+
ingestRecordCount = recordCount;
|
|
4566
|
+
ingestDurationMs = ingestRun?.durationMs;
|
|
4567
|
+
if (ingestScriptRan) {
|
|
4568
|
+
ingestOutcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
|
|
4569
|
+
if (ingestRecordCount != null) {
|
|
4570
|
+
track("AI Wizard Ingest Successful", {
|
|
4571
|
+
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
4572
|
+
record_count: ingestRecordCount,
|
|
4573
|
+
duration_ms: ingestDurationMs ?? 0
|
|
4722
4574
|
});
|
|
4723
|
-
const startedAt = Date.now();
|
|
4724
|
-
const run2 = await runIngestScript(
|
|
4725
|
-
worktree,
|
|
4726
|
-
ingestRuntime,
|
|
4727
|
-
ingestEntrypoint,
|
|
4728
|
-
{
|
|
4729
|
-
[APP_ID_VAR]: ingestApp.id,
|
|
4730
|
-
[API_KEY_VAR]: writeKey,
|
|
4731
|
-
[INDEX_NAME_VAR]: targetIndex
|
|
4732
|
-
}
|
|
4733
|
-
);
|
|
4734
|
-
ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
|
|
4735
|
-
ingestScriptRan = run2.ran && run2.ok;
|
|
4736
|
-
if (ingestScriptRan) {
|
|
4737
|
-
ingestDurationMs = Date.now() - startedAt;
|
|
4738
|
-
ingestRecordCount = parseIngestRecordCount(run2.output);
|
|
4739
|
-
if (ingestRecordCount != null) {
|
|
4740
|
-
track("AI Wizard Ingest Successful", {
|
|
4741
|
-
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
4742
|
-
record_count: ingestRecordCount,
|
|
4743
|
-
duration_ms: ingestDurationMs
|
|
4744
|
-
});
|
|
4745
|
-
}
|
|
4746
|
-
}
|
|
4747
|
-
let summaryLine;
|
|
4748
|
-
let outcomeMessage;
|
|
4749
|
-
if (!run2.ran) {
|
|
4750
|
-
summaryLine = `\u26A0\uFE0F Skipped running the ingestion script: ${run2.reason}`;
|
|
4751
|
-
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
|
|
4752
|
-
logger.warn(
|
|
4753
|
-
{
|
|
4754
|
-
runtime: ingestRuntime,
|
|
4755
|
-
entrypoint: ingestEntrypoint,
|
|
4756
|
-
reason: run2.reason
|
|
4757
|
-
},
|
|
4758
|
-
"implement: refused to auto-run ingestion script"
|
|
4759
|
-
);
|
|
4760
|
-
track("Error", {
|
|
4761
|
-
step: "Push Data",
|
|
4762
|
-
error: `ingestion script skipped: ${run2.reason}`,
|
|
4763
|
-
product_area: "AI Wizard"
|
|
4764
|
-
});
|
|
4765
|
-
} else if (run2.ok) {
|
|
4766
|
-
const status = "Ingestion run: succeeded.";
|
|
4767
|
-
summaryLine = run2.output ? `${status}
|
|
4768
|
-
${run2.output}` : status;
|
|
4769
|
-
outcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
|
|
4770
|
-
} else {
|
|
4771
|
-
const status = "\u26A0\uFE0F Ingestion run failed:";
|
|
4772
|
-
summaryLine = run2.output ? `${status}
|
|
4773
|
-
${run2.output}` : status;
|
|
4774
|
-
outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
|
|
4775
|
-
logger.warn(
|
|
4776
|
-
{
|
|
4777
|
-
runtime: ingestRuntime,
|
|
4778
|
-
entrypoint: ingestEntrypoint,
|
|
4779
|
-
output: run2.output
|
|
4780
|
-
},
|
|
4781
|
-
"implement: ingestion script run failed"
|
|
4782
|
-
);
|
|
4783
|
-
track("Error", {
|
|
4784
|
-
step: "Push Data",
|
|
4785
|
-
error: run2.output || "ingestion script exited non-zero",
|
|
4786
|
-
product_area: "AI Wizard"
|
|
4787
|
-
});
|
|
4788
|
-
}
|
|
4789
|
-
summaries.push(summaryLine);
|
|
4790
|
-
ingestOutcomeMessage = outcomeMessage;
|
|
4791
4575
|
}
|
|
4576
|
+
} else {
|
|
4577
|
+
const { reason, detail } = ingestFailure(ingestAttempt, executions);
|
|
4578
|
+
ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
|
|
4579
|
+
ingestFailureDetail = detail;
|
|
4580
|
+
summaries.push(
|
|
4581
|
+
`\u26A0\uFE0F Ingestion did not complete: ${reason}.${detail ? `
|
|
4582
|
+
${detail}` : ""}`
|
|
4583
|
+
);
|
|
4584
|
+
logger.warn(
|
|
4585
|
+
{
|
|
4586
|
+
ingestCommand,
|
|
4587
|
+
reason,
|
|
4588
|
+
approved: ingestAttempt?.approved,
|
|
4589
|
+
exitCode: ingestAttempt?.exitCode,
|
|
4590
|
+
timedOut: ingestAttempt?.timedOut,
|
|
4591
|
+
commandsRun: executions.length
|
|
4592
|
+
},
|
|
4593
|
+
"implement: ingestion script did not complete successfully"
|
|
4594
|
+
);
|
|
4595
|
+
track("Error", {
|
|
4596
|
+
step: "Push Data",
|
|
4597
|
+
error: `ingestion did not complete: ${reason}`,
|
|
4598
|
+
product_area: "AI Wizard"
|
|
4599
|
+
});
|
|
4792
4600
|
}
|
|
4793
4601
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
4794
|
-
if (
|
|
4795
|
-
commandMessages.push(
|
|
4796
|
-
`Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
|
|
4797
|
-
);
|
|
4602
|
+
if (ingestCommand) {
|
|
4603
|
+
commandMessages.push(`Ingestion command: ${ingestCommand}`);
|
|
4798
4604
|
}
|
|
4799
4605
|
await ctx.requestUserInput({
|
|
4800
4606
|
prompt: "",
|
|
4801
4607
|
promptType: "enterToContinue",
|
|
4802
4608
|
options: [],
|
|
4803
|
-
|
|
4609
|
+
// The wizard never streams command output, so a failed run's tail is the
|
|
4610
|
+
// only place the developer sees why it failed. One message per line:
|
|
4611
|
+
// the panel's height accounting counts a message as one wrapped line
|
|
4612
|
+
// (see Notices.tsx), so an embedded newline overflows it.
|
|
4613
|
+
messages: [
|
|
4614
|
+
ingestOutcomeMessage,
|
|
4615
|
+
...ingestFailureDetail?.split("\n").filter((l) => l.trim()) ?? [],
|
|
4616
|
+
...commandMessages
|
|
4617
|
+
]
|
|
4804
4618
|
});
|
|
4805
4619
|
}
|
|
4806
4620
|
if (useCases.includes("search")) {
|
|
@@ -4917,22 +4731,13 @@ ${run2.output}` : status;
|
|
|
4917
4731
|
"implement: agent reported success but no files changed in the worktree"
|
|
4918
4732
|
);
|
|
4919
4733
|
}
|
|
4920
|
-
if (installFailed) {
|
|
4921
|
-
summaries.push(
|
|
4922
|
-
'\u26A0\uFE0F Dependency install in the worktree failed. Run your package manager install in the worktree before the command below, or it will fail with "Cannot find module".'
|
|
4923
|
-
);
|
|
4924
|
-
}
|
|
4925
4734
|
return {
|
|
4926
4735
|
ingestionSource,
|
|
4927
4736
|
filesChanged,
|
|
4928
4737
|
summary: summaries.join("\n\n"),
|
|
4929
4738
|
worktreePath: worktree,
|
|
4930
|
-
...useCases.includes("ingestion") &&
|
|
4931
|
-
ingestCommand
|
|
4932
|
-
worktree,
|
|
4933
|
-
ingestRuntime,
|
|
4934
|
-
ingestEntrypoint
|
|
4935
|
-
),
|
|
4739
|
+
...useCases.includes("ingestion") && ingestCommand ? {
|
|
4740
|
+
ingestCommand,
|
|
4936
4741
|
ingestScriptRan,
|
|
4937
4742
|
...ingestRecordCount != null ? { ingestRecordCount } : {},
|
|
4938
4743
|
...ingestDurationMs != null ? { ingestDurationMs } : {}
|
|
@@ -4980,8 +4785,8 @@ var defaultWorkflow = {
|
|
|
4980
4785
|
defineStep({
|
|
4981
4786
|
id: "select-index",
|
|
4982
4787
|
title: "Set up index",
|
|
4983
|
-
outputSchema:
|
|
4984
|
-
selection:
|
|
4788
|
+
outputSchema: z28.object({
|
|
4789
|
+
selection: z28.string()
|
|
4985
4790
|
}),
|
|
4986
4791
|
run: (ctx) => selectIndexStep(ctx)
|
|
4987
4792
|
}),
|
|
@@ -5264,7 +5069,7 @@ function parseCliArgs(argv) {
|
|
|
5264
5069
|
|
|
5265
5070
|
// src/lib/resetState.ts
|
|
5266
5071
|
import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
5267
|
-
import { join as
|
|
5072
|
+
import { join as join11 } from "node:path";
|
|
5268
5073
|
var KEEP = ["wizard.log"];
|
|
5269
5074
|
async function resetProjectState() {
|
|
5270
5075
|
const dir = stateDir();
|
|
@@ -5278,7 +5083,7 @@ async function resetProjectState() {
|
|
|
5278
5083
|
const targets = entries.filter((name) => !KEEP.includes(name));
|
|
5279
5084
|
await Promise.all(
|
|
5280
5085
|
targets.map(
|
|
5281
|
-
(name) => rm2(
|
|
5086
|
+
(name) => rm2(join11(dir, name), { recursive: true, force: true })
|
|
5282
5087
|
)
|
|
5283
5088
|
);
|
|
5284
5089
|
return { dir, removed: targets };
|