@algolia/wizard 0.9.0-rc.71.62 → 0.9.0-rc.73.63
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
|
@@ -892,12 +892,12 @@ var sidebarItems = [
|
|
|
892
892
|
description: "push 100 records to Algolia in seconds"
|
|
893
893
|
},
|
|
894
894
|
{
|
|
895
|
-
title: "detect your
|
|
896
|
-
description: "React, Vue, Angular,
|
|
895
|
+
title: "detect your stack",
|
|
896
|
+
description: "React, Vue, Angular, Rails, Django, Laravel & more"
|
|
897
897
|
},
|
|
898
898
|
{
|
|
899
899
|
title: "scaffold a search UI",
|
|
900
|
-
description: "a styled InstantSearch
|
|
900
|
+
description: "a styled InstantSearch UI, wired into your app or templates"
|
|
901
901
|
},
|
|
902
902
|
{
|
|
903
903
|
title: "ship it",
|
|
@@ -1003,7 +1003,7 @@ var accessItems = [
|
|
|
1003
1003
|
{
|
|
1004
1004
|
tag: "READ",
|
|
1005
1005
|
title: "Project files",
|
|
1006
|
-
description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
1006
|
+
description: "reads your dependency manifests (package.json\u2026), configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
1007
1007
|
},
|
|
1008
1008
|
{
|
|
1009
1009
|
tag: "WRITE",
|
|
@@ -2221,15 +2221,282 @@ function writeCredentialsTool(ctx) {
|
|
|
2221
2221
|
// src/lib/tools/searchFiles.ts
|
|
2222
2222
|
import { tool as tool7 } from "ai";
|
|
2223
2223
|
import z10 from "zod";
|
|
2224
|
-
import { readdir as
|
|
2224
|
+
import { readdir as readdir3, readFile as readFile8 } from "node:fs/promises";
|
|
2225
|
+
import { join as join10 } from "node:path";
|
|
2226
|
+
|
|
2227
|
+
// src/lib/languages.ts
|
|
2228
|
+
import { readdir as readdir2, readFile as readFile7 } from "node:fs/promises";
|
|
2229
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
2230
|
+
import { join as join9 } from "node:path";
|
|
2231
|
+
|
|
2232
|
+
// src/lib/tools/utils/packageManager.ts
|
|
2233
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
2234
|
+
import { existsSync } from "node:fs";
|
|
2225
2235
|
import { join as join8 } from "node:path";
|
|
2236
|
+
var LOCKFILES = [
|
|
2237
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
2238
|
+
["yarn.lock", "yarn"],
|
|
2239
|
+
["bun.lockb", "bun"],
|
|
2240
|
+
["bun.lock", "bun"],
|
|
2241
|
+
["package-lock.json", "npm"]
|
|
2242
|
+
];
|
|
2243
|
+
async function readPackageJson(cwd = process.cwd()) {
|
|
2244
|
+
return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
|
|
2245
|
+
}
|
|
2246
|
+
function packageManagerFrom(pkg) {
|
|
2247
|
+
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2248
|
+
}
|
|
2249
|
+
function packageManagerFromLockfile(cwd) {
|
|
2250
|
+
return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
|
|
2251
|
+
}
|
|
2252
|
+
async function detectPackageManager(cwd) {
|
|
2253
|
+
try {
|
|
2254
|
+
const pkg = await readPackageJson(cwd);
|
|
2255
|
+
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2256
|
+
} catch {
|
|
2257
|
+
}
|
|
2258
|
+
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
// src/lib/shell.ts
|
|
2262
|
+
function shellQuote(value) {
|
|
2263
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
// src/lib/languages.ts
|
|
2267
|
+
var ENTRYPOINT_TOKEN = "{entrypoint}";
|
|
2268
|
+
var INGEST_DIR = ".algolia-wizard";
|
|
2269
|
+
var LANGUAGE_PROFILES = {
|
|
2270
|
+
javascript: {
|
|
2271
|
+
id: "javascript",
|
|
2272
|
+
displayName: "JavaScript/TypeScript",
|
|
2273
|
+
aliases: [
|
|
2274
|
+
"javascript",
|
|
2275
|
+
"js",
|
|
2276
|
+
"typescript",
|
|
2277
|
+
"ts",
|
|
2278
|
+
"node",
|
|
2279
|
+
"nodejs",
|
|
2280
|
+
"node.js",
|
|
2281
|
+
"bun",
|
|
2282
|
+
"deno",
|
|
2283
|
+
"ecmascript",
|
|
2284
|
+
"jsx",
|
|
2285
|
+
"tsx"
|
|
2286
|
+
],
|
|
2287
|
+
manifests: ["package.json"],
|
|
2288
|
+
// The concrete npm-family manager is resolved by detectPackageManager (it
|
|
2289
|
+
// honours the package.json `packageManager` field, which lockfiles can't
|
|
2290
|
+
// express), so one spec covers all four and `resolveToolchain` rewrites the
|
|
2291
|
+
// binary below.
|
|
2292
|
+
packageManagers: [
|
|
2293
|
+
{
|
|
2294
|
+
id: "npm",
|
|
2295
|
+
dependency: { mode: "agent-declares", file: "package.json" },
|
|
2296
|
+
installSteps: [{ argv: ["npm", "install"] }],
|
|
2297
|
+
ingest: {
|
|
2298
|
+
kind: "auto",
|
|
2299
|
+
argv: ["node", ENTRYPOINT_TOKEN],
|
|
2300
|
+
entrypointExtensions: [".mjs", ".cjs", ".js"]
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
],
|
|
2304
|
+
sdk: { packageName: "algoliasearch", versionPin: "^5", docKey: "js" },
|
|
2305
|
+
ingestEntrypointExample: `${INGEST_DIR}/ingest.mjs`,
|
|
2306
|
+
// package.json scripts are repo-defined, so they're resolved at run time by
|
|
2307
|
+
// repoVerification rather than listed here.
|
|
2308
|
+
verification: [],
|
|
2309
|
+
envReadInstruction: "Read them from `process.env`.",
|
|
2310
|
+
skipDirs: ["node_modules", "dist", "build", "coverage", ".next", "out"]
|
|
2311
|
+
}
|
|
2312
|
+
};
|
|
2313
|
+
var DEFAULT_LANGUAGE_ID = "javascript";
|
|
2314
|
+
var JAVASCRIPT = "javascript";
|
|
2315
|
+
var CURATED_LANGUAGES = Object.values(
|
|
2316
|
+
LANGUAGE_PROFILES
|
|
2317
|
+
).map((profile) => profile.displayName);
|
|
2318
|
+
function isBackendLanguage(profile) {
|
|
2319
|
+
return profile.id !== JAVASCRIPT;
|
|
2320
|
+
}
|
|
2321
|
+
function normalizeLanguageName(name) {
|
|
2322
|
+
return name.toLowerCase().trim().replace(/[\s_-]+/g, "");
|
|
2323
|
+
}
|
|
2324
|
+
var ALIAS_TO_ID = /* @__PURE__ */ new Map();
|
|
2325
|
+
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
2326
|
+
for (const alias of [profile.id, profile.displayName, ...profile.aliases]) {
|
|
2327
|
+
ALIAS_TO_ID.set(normalizeLanguageName(alias), profile.id);
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
function resolveLanguageProfile(name) {
|
|
2331
|
+
const id = ALIAS_TO_ID.get(normalizeLanguageName(name));
|
|
2332
|
+
return id ? LANGUAGE_PROFILES[id] : void 0;
|
|
2333
|
+
}
|
|
2334
|
+
function isSameLanguage(a, b) {
|
|
2335
|
+
const x = resolveLanguageProfile(a);
|
|
2336
|
+
const y = resolveLanguageProfile(b);
|
|
2337
|
+
if (x && y) return x.id === y.id;
|
|
2338
|
+
if (x || y) return false;
|
|
2339
|
+
const folded = normalizeLanguageName(a);
|
|
2340
|
+
return folded !== "" && folded === normalizeLanguageName(b);
|
|
2341
|
+
}
|
|
2342
|
+
var BASE_SKIP_DIRS = ["node_modules", ".git", "dist"];
|
|
2343
|
+
var ALL_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2344
|
+
...BASE_SKIP_DIRS,
|
|
2345
|
+
...Object.values(LANGUAGE_PROFILES).flatMap((p) => p.skipDirs)
|
|
2346
|
+
]);
|
|
2347
|
+
var ALLOWED_BINARIES = new Set(
|
|
2348
|
+
Object.values(LANGUAGE_PROFILES).flatMap((profile) => [
|
|
2349
|
+
...profile.packageManagers.flatMap((pm) => [
|
|
2350
|
+
...pm.installSteps.map((s) => s.argv[0]),
|
|
2351
|
+
...pm.ingest.kind === "auto" ? [pm.ingest.argv[0]] : []
|
|
2352
|
+
]),
|
|
2353
|
+
...profile.verification.map((v) => v.argv[0])
|
|
2354
|
+
])
|
|
2355
|
+
);
|
|
2356
|
+
var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
|
|
2357
|
+
function isWorktreeRelativeCommand(command) {
|
|
2358
|
+
return command.includes("/");
|
|
2359
|
+
}
|
|
2360
|
+
function withCommand(argv, command) {
|
|
2361
|
+
return [command, ...argv.slice(1)];
|
|
2362
|
+
}
|
|
2363
|
+
function resolveDeclaredManifest(root, packageManager) {
|
|
2364
|
+
const { dependency } = packageManager;
|
|
2365
|
+
if (dependency.mode !== "agent-declares" || !dependency.alternatives?.length) {
|
|
2366
|
+
return packageManager;
|
|
2367
|
+
}
|
|
2368
|
+
const present = [dependency.file, ...dependency.alternatives].find(
|
|
2369
|
+
(file) => existsSync2(join9(root, file))
|
|
2370
|
+
);
|
|
2371
|
+
if (!present || present === dependency.file) return packageManager;
|
|
2372
|
+
return { ...packageManager, dependency: { ...dependency, file: present } };
|
|
2373
|
+
}
|
|
2374
|
+
async function manifestPresent(root, manifest, listing) {
|
|
2375
|
+
if (!manifest.startsWith("*.")) return existsSync2(join9(root, manifest));
|
|
2376
|
+
if (!listing.entries) {
|
|
2377
|
+
const entries = await readdir2(root).catch(() => []);
|
|
2378
|
+
listing.entries = Array.isArray(entries) ? entries : [];
|
|
2379
|
+
}
|
|
2380
|
+
const suffix = manifest.slice(1);
|
|
2381
|
+
return listing.entries.some((e) => e.endsWith(suffix));
|
|
2382
|
+
}
|
|
2383
|
+
async function profileManifestPresent(root, profile, listing) {
|
|
2384
|
+
for (const manifest of profile.manifests) {
|
|
2385
|
+
if (await manifestPresent(root, manifest, listing)) return true;
|
|
2386
|
+
}
|
|
2387
|
+
return false;
|
|
2388
|
+
}
|
|
2389
|
+
async function detectProfilesFromManifests(root) {
|
|
2390
|
+
const listing = {};
|
|
2391
|
+
const found = [];
|
|
2392
|
+
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
2393
|
+
if (await profileManifestPresent(root, profile, listing)) found.push(profile);
|
|
2394
|
+
}
|
|
2395
|
+
return found;
|
|
2396
|
+
}
|
|
2397
|
+
async function hasProfileManifest(root, profile) {
|
|
2398
|
+
return profileManifestPresent(root, profile, {});
|
|
2399
|
+
}
|
|
2400
|
+
async function pickIngestionCandidates(root, confirmedNames) {
|
|
2401
|
+
const confirmed3 = confirmedNames.map((name) => resolveLanguageProfile(name)).filter((p) => p !== void 0);
|
|
2402
|
+
const onDisk = await detectProfilesFromManifests(root);
|
|
2403
|
+
const onDiskIds = new Set(onDisk.map((p) => p.id));
|
|
2404
|
+
const candidates = [
|
|
2405
|
+
...new Map(
|
|
2406
|
+
confirmed3.filter((p) => onDiskIds.has(p.id)).map((p) => [p.id, p])
|
|
2407
|
+
).values()
|
|
2408
|
+
];
|
|
2409
|
+
return { candidates, confirmed: confirmed3, onDisk };
|
|
2410
|
+
}
|
|
2411
|
+
async function resolveToolchain(root, profile) {
|
|
2412
|
+
const signals = (pm) => [
|
|
2413
|
+
...pm.lockfiles ?? [],
|
|
2414
|
+
...pm.detectFiles ?? []
|
|
2415
|
+
];
|
|
2416
|
+
const matched = profile.packageManagers.find(
|
|
2417
|
+
(pm) => signals(pm).some((f) => existsSync2(join9(root, f)))
|
|
2418
|
+
);
|
|
2419
|
+
const fallback = profile.packageManagers.find((pm) => signals(pm).length === 0) ?? profile.packageManagers[0];
|
|
2420
|
+
const packageManager = resolveDeclaredManifest(root, matched ?? fallback);
|
|
2421
|
+
let { installSteps, ingest } = packageManager;
|
|
2422
|
+
installSteps = installSteps.map(
|
|
2423
|
+
(step) => isWorktreeRelativeCommand(step.argv[0]) ? { ...step, argv: withCommand(step.argv, join9(root, step.argv[0])) } : step
|
|
2424
|
+
);
|
|
2425
|
+
if (ingest.kind === "auto" && isWorktreeRelativeCommand(ingest.argv[0])) {
|
|
2426
|
+
ingest = {
|
|
2427
|
+
...ingest,
|
|
2428
|
+
argv: withCommand(ingest.argv, join9(root, ingest.argv[0]))
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
if (profile.id === "javascript") {
|
|
2432
|
+
const pm = await detectPackageManager(root);
|
|
2433
|
+
if (JS_PACKAGE_MANAGERS.has(pm)) {
|
|
2434
|
+
installSteps = installSteps.map((step) => ({
|
|
2435
|
+
...step,
|
|
2436
|
+
argv: withCommand(step.argv, pm)
|
|
2437
|
+
}));
|
|
2438
|
+
if (pm === "bun" && ingest.kind === "auto") {
|
|
2439
|
+
ingest = { ...ingest, argv: withCommand(ingest.argv, "bun") };
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
return { profile, packageManager, installSteps, ingest };
|
|
2444
|
+
}
|
|
2445
|
+
function resolveIngestArgv(ingest, entrypoint) {
|
|
2446
|
+
if (ingest.kind !== "auto") {
|
|
2447
|
+
throw new Error("resolveIngestArgv called for a manual-run toolchain");
|
|
2448
|
+
}
|
|
2449
|
+
return ingest.argv.map(
|
|
2450
|
+
(part) => part === ENTRYPOINT_TOKEN ? entrypoint : part
|
|
2451
|
+
);
|
|
2452
|
+
}
|
|
2453
|
+
function describeIngestCommand(ingest, entrypoint) {
|
|
2454
|
+
if (ingest.kind !== "auto") return ingest.runCommand;
|
|
2455
|
+
return ingest.argv.map((part) => part === ENTRYPOINT_TOKEN ? shellQuote(entrypoint) : part).join(" ");
|
|
2456
|
+
}
|
|
2457
|
+
function ingestScriptDir(profile) {
|
|
2458
|
+
const parts = profile.ingestEntrypointExample.split("/");
|
|
2459
|
+
return parts.slice(0, -1).join("/") || ".";
|
|
2460
|
+
}
|
|
2461
|
+
function localSourceLimitation(root, profile) {
|
|
2462
|
+
const caveat = profile.localSourceCaveat;
|
|
2463
|
+
if (!caveat) return void 0;
|
|
2464
|
+
return existsSync2(join9(root, caveat.unless)) ? void 0 : caveat.message;
|
|
2465
|
+
}
|
|
2466
|
+
async function missingBuildTask(root, toolchain) {
|
|
2467
|
+
const { ingest, packageManager } = toolchain;
|
|
2468
|
+
if (ingest.kind !== "manual" || !ingest.requiresBuildTask) return void 0;
|
|
2469
|
+
if (packageManager.dependency.mode !== "agent-declares") return void 0;
|
|
2470
|
+
const buildFile = join9(root, packageManager.dependency.file);
|
|
2471
|
+
const contents = await readFile7(buildFile, "utf8").catch(() => void 0);
|
|
2472
|
+
if (contents === void 0) return void 0;
|
|
2473
|
+
return contents.includes(ingest.requiresBuildTask) ? void 0 : ingest.requiresBuildTask;
|
|
2474
|
+
}
|
|
2475
|
+
function sdkVersionPin(profile, packageManager) {
|
|
2476
|
+
return packageManager.sdkVersionPin ?? profile.sdk.versionPin;
|
|
2477
|
+
}
|
|
2478
|
+
function dependencyInstruction(toolchain) {
|
|
2479
|
+
const { profile, packageManager } = toolchain;
|
|
2480
|
+
const { packageName } = profile.sdk;
|
|
2481
|
+
const versionPin = sdkVersionPin(profile, packageManager);
|
|
2482
|
+
const also = profile.sdk.alsoRequires ? ` ${profile.sdk.alsoRequires}` : "";
|
|
2483
|
+
switch (packageManager.dependency.mode) {
|
|
2484
|
+
case "wizard-installs":
|
|
2485
|
+
return `The wizard installs ${packageName} ${versionPin} in the worktree after you finish \u2014 import it directly and do not edit dependency manifests for it.${also}`;
|
|
2486
|
+
case "code-imports":
|
|
2487
|
+
return `Import ${packageName} in the script; the wizard resolves and fetches it in the worktree after you finish. Do not edit dependency manifests by hand.${also}`;
|
|
2488
|
+
case "agent-declares":
|
|
2489
|
+
return `Declare ${packageName} ${versionPin} in "${packageManager.dependency.file}" (create the file if needed), plus any other dependency your script imports; the wizard installs them in the worktree after you finish.${also}`;
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
// src/lib/tools/searchFiles.ts
|
|
2226
2494
|
var MAX_QUERY_LENGTH = 1e3;
|
|
2227
2495
|
async function walkFiles(dir) {
|
|
2228
|
-
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
2229
2496
|
const out = [];
|
|
2230
|
-
for (const e of await
|
|
2231
|
-
if (e.name.startsWith(".") ||
|
|
2232
|
-
const full =
|
|
2497
|
+
for (const e of await readdir3(dir, { withFileTypes: true })) {
|
|
2498
|
+
if (e.name.startsWith(".") || ALL_SKIP_DIRS.has(e.name)) continue;
|
|
2499
|
+
const full = join10(dir, e.name);
|
|
2233
2500
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2234
2501
|
else if (e.isFile()) out.push(full);
|
|
2235
2502
|
}
|
|
@@ -2262,7 +2529,7 @@ function searchFilesTool(ctx) {
|
|
|
2262
2529
|
for (const file of await walkFiles(resolved.target)) {
|
|
2263
2530
|
let content;
|
|
2264
2531
|
try {
|
|
2265
|
-
content = await
|
|
2532
|
+
content = await readFile8(file, "utf8");
|
|
2266
2533
|
} catch {
|
|
2267
2534
|
continue;
|
|
2268
2535
|
}
|
|
@@ -2286,6 +2553,10 @@ function searchFilesTool(ctx) {
|
|
|
2286
2553
|
import { tool as tool8 } from "ai";
|
|
2287
2554
|
import z11 from "zod";
|
|
2288
2555
|
|
|
2556
|
+
// src/lib/tools/repoVerification.ts
|
|
2557
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
2558
|
+
import { join as join11 } from "node:path";
|
|
2559
|
+
|
|
2289
2560
|
// src/lib/tools/utils/runCommand.ts
|
|
2290
2561
|
import { spawn as spawn2 } from "node:child_process";
|
|
2291
2562
|
var INSTALL_TIMEOUT_MS = 15 * 6e4;
|
|
@@ -2337,71 +2608,89 @@ Timed out after ${seconds}s: ${command} ${args.join(" ")}`.trim(),
|
|
|
2337
2608
|
});
|
|
2338
2609
|
}
|
|
2339
2610
|
|
|
2340
|
-
// src/lib/tools/utils/packageManager.ts
|
|
2341
|
-
import { readFile as readFile7 } from "node:fs/promises";
|
|
2342
|
-
import { existsSync } from "node:fs";
|
|
2343
|
-
import { join as join9 } from "node:path";
|
|
2344
|
-
var LOCKFILES = [
|
|
2345
|
-
["pnpm-lock.yaml", "pnpm"],
|
|
2346
|
-
["yarn.lock", "yarn"],
|
|
2347
|
-
["bun.lockb", "bun"],
|
|
2348
|
-
["bun.lock", "bun"],
|
|
2349
|
-
["package-lock.json", "npm"]
|
|
2350
|
-
];
|
|
2351
|
-
async function readPackageJson(cwd = process.cwd()) {
|
|
2352
|
-
return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
|
|
2353
|
-
}
|
|
2354
|
-
function packageManagerFrom(pkg) {
|
|
2355
|
-
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2356
|
-
}
|
|
2357
|
-
function packageManagerFromLockfile(cwd) {
|
|
2358
|
-
return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
|
|
2359
|
-
}
|
|
2360
|
-
async function detectPackageManager(cwd) {
|
|
2361
|
-
try {
|
|
2362
|
-
const pkg = await readPackageJson(cwd);
|
|
2363
|
-
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2364
|
-
} catch {
|
|
2365
|
-
}
|
|
2366
|
-
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2367
|
-
}
|
|
2368
|
-
|
|
2369
2611
|
// src/lib/tools/repoVerification.ts
|
|
2370
2612
|
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
2371
|
-
async function
|
|
2613
|
+
async function runCheck(command, binary, args) {
|
|
2614
|
+
const { code, output } = await runCommand(binary, args, {
|
|
2615
|
+
timeoutMs: VERIFY_TIMEOUT_MS
|
|
2616
|
+
});
|
|
2617
|
+
return { command, exitCode: code, ok: code === 0, output: output.trim() };
|
|
2618
|
+
}
|
|
2619
|
+
async function javascriptChecks() {
|
|
2372
2620
|
let pkg;
|
|
2373
2621
|
try {
|
|
2374
2622
|
pkg = await readPackageJson();
|
|
2375
2623
|
} catch (err) {
|
|
2376
|
-
|
|
2377
|
-
|
|
2624
|
+
return {
|
|
2625
|
+
limitation: `Could not read package.json to detect verification conventions: ${err.message}`
|
|
2626
|
+
};
|
|
2378
2627
|
}
|
|
2379
2628
|
const scripts = pkg.scripts ?? {};
|
|
2380
2629
|
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
2381
2630
|
if (present.length === 0) {
|
|
2382
|
-
|
|
2383
|
-
|
|
2631
|
+
return {
|
|
2632
|
+
limitation: `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`
|
|
2633
|
+
};
|
|
2384
2634
|
}
|
|
2385
2635
|
const pm = await detectPackageManager(process.cwd());
|
|
2386
2636
|
const checks = [];
|
|
2387
2637
|
for (const script of present) {
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
});
|
|
2392
|
-
checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
|
|
2638
|
+
checks.push(
|
|
2639
|
+
await runCheck(`${pm} run ${script}`, pm, ["run", script])
|
|
2640
|
+
);
|
|
2393
2641
|
}
|
|
2394
|
-
return {
|
|
2642
|
+
return { checks };
|
|
2643
|
+
}
|
|
2644
|
+
async function registryChecks(id) {
|
|
2645
|
+
const profile = LANGUAGE_PROFILES[id];
|
|
2646
|
+
const runnable = profile.verification.filter(
|
|
2647
|
+
(spec) => !spec.requiresFile || existsSync3(join11(process.cwd(), spec.requiresFile))
|
|
2648
|
+
);
|
|
2649
|
+
if (runnable.length === 0) {
|
|
2650
|
+
return {
|
|
2651
|
+
limitation: `No mechanical verification available for ${profile.displayName} in this repo.`
|
|
2652
|
+
};
|
|
2653
|
+
}
|
|
2654
|
+
const checks = [];
|
|
2655
|
+
for (const spec of runnable) {
|
|
2656
|
+
checks.push(
|
|
2657
|
+
await runCheck(spec.argv.join(" "), spec.argv[0], [...spec.argv.slice(1)])
|
|
2658
|
+
);
|
|
2659
|
+
}
|
|
2660
|
+
return { checks };
|
|
2661
|
+
}
|
|
2662
|
+
async function runRepoVerificationCheck(languages = [DEFAULT_LANGUAGE_ID]) {
|
|
2663
|
+
const ids = [...new Set(languages)];
|
|
2664
|
+
if (ids.length === 0) ids.push(DEFAULT_LANGUAGE_ID);
|
|
2665
|
+
const checks = [];
|
|
2666
|
+
const limitations = [];
|
|
2667
|
+
for (const id of ids) {
|
|
2668
|
+
const result = id === JAVASCRIPT ? await javascriptChecks() : await registryChecks(id);
|
|
2669
|
+
if ("checks" in result) checks.push(...result.checks);
|
|
2670
|
+
else limitations.push(result.limitation);
|
|
2671
|
+
}
|
|
2672
|
+
if (checks.length === 0) {
|
|
2673
|
+
return {
|
|
2674
|
+
ok: false,
|
|
2675
|
+
checks: [],
|
|
2676
|
+
limitation: limitations.join(" ") || "No verification checks available."
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
2679
|
+
return {
|
|
2680
|
+
ok: checks.every((c) => c.ok),
|
|
2681
|
+
checks,
|
|
2682
|
+
...limitations.length ? { limitation: limitations.join(" ") } : {}
|
|
2683
|
+
};
|
|
2395
2684
|
}
|
|
2396
2685
|
|
|
2397
2686
|
// src/lib/tools/verifyImplementation.ts
|
|
2398
|
-
function verifyImplementationTool() {
|
|
2687
|
+
function verifyImplementationTool(ctx) {
|
|
2399
2688
|
return tool8({
|
|
2400
|
-
description: "Run the repo's mechanical verification
|
|
2689
|
+
description: "Run the repo's mechanical verification checks for generated implementation changes. Uses the conventions of the repo's languages (package.json lint/typecheck/check scripts for JavaScript, the equivalent compile/analyze command elsewhere) and returns structured pass/fail evidence for the verifier to interpret.",
|
|
2401
2690
|
inputSchema: z11.object(),
|
|
2402
2691
|
execute: async () => {
|
|
2403
|
-
logger.info("called verifyImplementation tool");
|
|
2404
|
-
return runRepoVerificationCheck();
|
|
2692
|
+
logger.info({ languages: ctx.languages }, "called verifyImplementation tool");
|
|
2693
|
+
return runRepoVerificationCheck(ctx.languages);
|
|
2405
2694
|
}
|
|
2406
2695
|
});
|
|
2407
2696
|
}
|
|
@@ -2527,12 +2816,17 @@ var DEFAULT_TOOL_LIMITS = {
|
|
|
2527
2816
|
read: 20,
|
|
2528
2817
|
match: 100
|
|
2529
2818
|
};
|
|
2530
|
-
function createToolContext(
|
|
2819
|
+
function createToolContext({
|
|
2820
|
+
limits = DEFAULT_TOOL_LIMITS,
|
|
2821
|
+
cwd = process.cwd(),
|
|
2822
|
+
languages = [DEFAULT_LANGUAGE_ID]
|
|
2823
|
+
} = {}) {
|
|
2531
2824
|
return {
|
|
2532
2825
|
root: cwd,
|
|
2533
2826
|
cwd,
|
|
2534
2827
|
limits,
|
|
2535
|
-
counts: { list: 0, search: 0, read: 0 }
|
|
2828
|
+
counts: { list: 0, search: 0, read: 0 },
|
|
2829
|
+
languages: languages.length ? languages : [DEFAULT_LANGUAGE_ID]
|
|
2536
2830
|
};
|
|
2537
2831
|
}
|
|
2538
2832
|
|
|
@@ -2569,7 +2863,7 @@ function createTools(ctx, { output, tools }) {
|
|
|
2569
2863
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
2570
2864
|
verifyImplementation: withLogging(
|
|
2571
2865
|
"verifyImplementation",
|
|
2572
|
-
verifyImplementationTool()
|
|
2866
|
+
verifyImplementationTool(ctx)
|
|
2573
2867
|
),
|
|
2574
2868
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
2575
2869
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
@@ -2605,7 +2899,7 @@ async function runAgent(req) {
|
|
|
2605
2899
|
baseURL: PROXY_BASE_URL,
|
|
2606
2900
|
fetch: proxyFetch
|
|
2607
2901
|
});
|
|
2608
|
-
const toolContext = createToolContext();
|
|
2902
|
+
const toolContext = createToolContext({ languages: req.languages });
|
|
2609
2903
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
2610
2904
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
2611
2905
|
const instructions = [
|
|
@@ -2696,8 +2990,11 @@ var detectLanguageSchema = z16.object({
|
|
|
2696
2990
|
var detectLanguage = () => runAgent({
|
|
2697
2991
|
instructions: [
|
|
2698
2992
|
"Analyze the codebase and determine the programming languages and frameworks used",
|
|
2993
|
+
"Start from the dependency manifests: package.json.",
|
|
2994
|
+
"List the language that owns the backend/data code first \u2014 that is the one an ingestion script will be written in.",
|
|
2699
2995
|
"If a superset language is found, exclude the subset language. TS-over-JS.",
|
|
2700
2996
|
"If a meta-framework is used, exclude the framework. Next-over-React.",
|
|
2997
|
+
"Frameworks include backend and server-rendering frameworks (e.g. Rails, Django, Laravel, Symfony, Spring Boot, ASP.NET Core, Flask, Gin, Ktor) as well as frontend ones (React, Vue, Angular, Svelte) and mobile ones (Flutter, SwiftUI).",
|
|
2701
2998
|
"Return the exact version",
|
|
2702
2999
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
2703
3000
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
@@ -2746,6 +3043,7 @@ var MODE_CONFIG = {
|
|
|
2746
3043
|
"Analyze the codebase to find the data entities (models) that should be ingested into Algolia.",
|
|
2747
3044
|
"For each entity, return its name, the file path(s) where it is defined, and its indexable attribute keys (the fields a user would search or filter on).",
|
|
2748
3045
|
"Inspect the source of each entity to extract real field names for attributes \u2014 do not guess or leave attributes empty.",
|
|
3046
|
+
"Entities live wherever the stack keeps them: TypeScript interfaces or a Prisma schema.",
|
|
2749
3047
|
"Prefer domain models (e.g. Document, Product, User) over framework or infrastructure types.",
|
|
2750
3048
|
"Use as few tools as possible, but do not guess. If you cannot find any entities, return an empty array.",
|
|
2751
3049
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
@@ -2757,8 +3055,9 @@ var MODE_CONFIG = {
|
|
|
2757
3055
|
instructions: [
|
|
2758
3056
|
"Analyze the codebase to determine the single best location to add search UI functionality.",
|
|
2759
3057
|
"Prefer a shared, always-rendered layout location (e.g. a header or navigation component) so search is reachable across the app.",
|
|
2760
|
-
"
|
|
2761
|
-
|
|
3058
|
+
"It may be a client-side component or a server-rendered template \u2014 return whichever the project actually renders its UI from (e.g. /layouts/header.tsx, app/views/layouts/application.html.erb, templates/base.html, resources/views/layouts/app.blade.php, templates/base.html.twig).",
|
|
3059
|
+
"Return one file path as searchImplementationAnalysis.",
|
|
3060
|
+
'Use as few tools as possible, but do not guess. If the project renders no UI at all (an API-only service), say "unknown".',
|
|
2762
3061
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
2763
3062
|
"When done, call reportStatus"
|
|
2764
3063
|
],
|
|
@@ -2767,7 +3066,7 @@ var MODE_CONFIG = {
|
|
|
2767
3066
|
verification: {
|
|
2768
3067
|
instructions: [
|
|
2769
3068
|
"Analyze the codebase to determine which code-quality tools are available to validate changes.",
|
|
2770
|
-
"Look at
|
|
3069
|
+
"Look at the dependency manifest and config files for the project's languages: package.json scripts with tsconfig/eslint/prettier.",
|
|
2771
3070
|
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"].',
|
|
2772
3071
|
"Use as few tools as possible, but do not guess. If you cannot find any, return an empty array.",
|
|
2773
3072
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
@@ -2795,7 +3094,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
2795
3094
|
// package.json
|
|
2796
3095
|
var package_default = {
|
|
2797
3096
|
name: "@algolia/wizard",
|
|
2798
|
-
version: "0.9.0-rc.
|
|
3097
|
+
version: "0.9.0-rc.73.63",
|
|
2799
3098
|
description: "Magically implement Algolia functionality in your codebase",
|
|
2800
3099
|
type: "module",
|
|
2801
3100
|
engines: {
|
|
@@ -2898,82 +3197,185 @@ function parseEntries(raw) {
|
|
|
2898
3197
|
return raw.split(",").map(clean).filter(Boolean).slice(0, MAX_ENTRIES).map((name) => ({ name, version: "unknown" }));
|
|
2899
3198
|
}
|
|
2900
3199
|
var summarize = (entries) => entries.length ? entries.map((e) => e.name).join(", ") : "none";
|
|
2901
|
-
|
|
3200
|
+
|
|
3201
|
+
// src/actions/confirmLanguage.ts
|
|
3202
|
+
import z19 from "zod";
|
|
3203
|
+
var confirmLanguageSchema = z19.object({
|
|
3204
|
+
languages: detectLanguageSchema.shape.languages
|
|
3205
|
+
});
|
|
3206
|
+
var OTHER_OPTION = "Other";
|
|
3207
|
+
function confirmed(languages) {
|
|
3208
|
+
track("AI Wizard Language Confirmed", { languages });
|
|
3209
|
+
return { languages };
|
|
3210
|
+
}
|
|
3211
|
+
async function askOtherLanguage(ctx) {
|
|
3212
|
+
let prompt = "enter the language for your ingestion script";
|
|
2902
3213
|
for (; ; ) {
|
|
2903
3214
|
const answer = await ctx.requestUserInput({
|
|
2904
3215
|
prompt,
|
|
2905
3216
|
promptType: "textInput",
|
|
2906
|
-
options: []
|
|
2907
|
-
helpText: 'Comma-separated, e.g. "TypeScript, Node".'
|
|
3217
|
+
options: []
|
|
2908
3218
|
});
|
|
2909
3219
|
if (typeof answer !== "string") {
|
|
2910
|
-
throw new Error("
|
|
3220
|
+
throw new Error("confirmLanguage received an unexpected non-text result");
|
|
2911
3221
|
}
|
|
2912
|
-
const
|
|
2913
|
-
if (
|
|
2914
|
-
prompt = "
|
|
3222
|
+
const name = parseEntries(answer)[0]?.name;
|
|
3223
|
+
if (name) return name;
|
|
3224
|
+
prompt = "please enter a language name:";
|
|
2915
3225
|
}
|
|
2916
3226
|
}
|
|
2917
|
-
|
|
2918
|
-
// src/actions/confirmLanguage.ts
|
|
2919
|
-
import z19 from "zod";
|
|
2920
|
-
var confirmLanguageSchema = z19.object({
|
|
2921
|
-
languages: detectLanguageSchema.shape.languages
|
|
2922
|
-
});
|
|
2923
3227
|
async function confirmLanguage(ctx) {
|
|
2924
3228
|
const detected = ctx.getStepOutput("project-scan");
|
|
2925
|
-
const
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
3229
|
+
const detectedLanguages = detected.languages ?? [];
|
|
3230
|
+
const others = (chosen) => detectedLanguages.filter((l) => !isSameLanguage(l.name, chosen));
|
|
3231
|
+
const primary = detectedLanguages[0];
|
|
3232
|
+
if (primary) {
|
|
3233
|
+
const accepted = await ctx.requestUserInput({
|
|
3234
|
+
prompt: `Write the ingestion script in ${primary.name}?`,
|
|
3235
|
+
promptType: "acceptReject",
|
|
3236
|
+
options: [`Confirm ${primary.name}`, "Use a different language"],
|
|
3237
|
+
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0],
|
|
3238
|
+
messages: detectedLanguages.length > 1 ? [`Detected: ${summarize(detectedLanguages)}`] : []
|
|
3239
|
+
});
|
|
3240
|
+
if (accepted === true) return confirmed(detectedLanguages);
|
|
3241
|
+
}
|
|
3242
|
+
const options = [...CURATED_LANGUAGES];
|
|
3243
|
+
for (const language of detectedLanguages) {
|
|
3244
|
+
if (!options.some((o) => isSameLanguage(o, language.name))) {
|
|
3245
|
+
options.push(language.name);
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
options.push(OTHER_OPTION);
|
|
3249
|
+
const detectedFor = (option) => detectedLanguages.find((l) => isSameLanguage(option, l.name));
|
|
3250
|
+
const secondary = options.map(
|
|
3251
|
+
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
3252
|
+
);
|
|
3253
|
+
const defaultSelectedIndex = Math.max(
|
|
3254
|
+
options.findIndex((o) => detectedFor(o)),
|
|
3255
|
+
0
|
|
3256
|
+
);
|
|
3257
|
+
const selection = await ctx.requestUserInput({
|
|
3258
|
+
prompt: "select the language for your ingestion script",
|
|
3259
|
+
promptType: "multipleChoice",
|
|
3260
|
+
options,
|
|
3261
|
+
secondary,
|
|
3262
|
+
defaultSelectedIndex
|
|
2936
3263
|
});
|
|
2937
|
-
|
|
3264
|
+
if (typeof selection !== "string") {
|
|
3265
|
+
throw new Error("confirmLanguage received an unexpected non-text result");
|
|
3266
|
+
}
|
|
3267
|
+
const name = selection === OTHER_OPTION ? await askOtherLanguage(ctx) : selection;
|
|
3268
|
+
const version = detectedFor(name)?.version ?? "unknown";
|
|
3269
|
+
return confirmed([{ name, version }, ...others(name)]);
|
|
2938
3270
|
}
|
|
2939
3271
|
|
|
2940
3272
|
// src/actions/confirmFramework.ts
|
|
2941
3273
|
import z20 from "zod";
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
var
|
|
2946
|
-
|
|
2947
|
-
"
|
|
2948
|
-
"
|
|
2949
|
-
"
|
|
2950
|
-
"
|
|
2951
|
-
|
|
3274
|
+
|
|
3275
|
+
// src/lib/frameworks.ts
|
|
3276
|
+
var BACKEND_ONLY_FRAMEWORK = "Backend only / API";
|
|
3277
|
+
var FRAMEWORKS = [
|
|
3278
|
+
// Frontend — InstantSearch component flavors.
|
|
3279
|
+
{ name: "Next.js", strategy: "react", aliases: ["next", "nextjs"] },
|
|
3280
|
+
{ name: "React", strategy: "react", aliases: ["reactjs"] },
|
|
3281
|
+
{ name: "Vue", strategy: "vue", aliases: ["vuejs", "nuxt", "nuxtjs"] },
|
|
3282
|
+
{ name: "Angular", strategy: "angular", aliases: ["angularjs"] },
|
|
3283
|
+
// No Svelte InstantSearch flavor exists, so it uses InstantSearch.js.
|
|
3284
|
+
{ name: "Svelte", strategy: "js", aliases: ["sveltekit"] },
|
|
3285
|
+
{
|
|
3286
|
+
name: "Vanilla JS",
|
|
3287
|
+
strategy: "js",
|
|
3288
|
+
aliases: ["vanilla", "javascript", "js", "astro", "vite"]
|
|
3289
|
+
},
|
|
3290
|
+
// Backend — Algolia's official framework integrations. Server-rendered
|
|
3291
|
+
// templates get InstantSearch.js from a CDN.
|
|
3292
|
+
{
|
|
3293
|
+
name: "Rails",
|
|
3294
|
+
strategy: "cdn-template",
|
|
3295
|
+
aliases: ["rubyonrails", "ruby on rails", "erb"]
|
|
3296
|
+
},
|
|
3297
|
+
{ name: "Django", strategy: "cdn-template", aliases: ["jinja", "jinja2"] },
|
|
3298
|
+
{ name: "Laravel", strategy: "cdn-template", aliases: ["blade"] },
|
|
3299
|
+
{ name: "Symfony", strategy: "cdn-template", aliases: ["twig"] },
|
|
3300
|
+
// Mobile — Algolia ships InstantSearch iOS/Android and Dart clients, but the
|
|
3301
|
+
// wizard can't scaffold a native UI, so it points at the docs instead.
|
|
3302
|
+
{ name: "Flutter", strategy: "none", aliases: [] },
|
|
3303
|
+
{ name: "iOS", strategy: "none", aliases: ["swiftui", "uikit"] },
|
|
3304
|
+
{ name: "Android", strategy: "none", aliases: ["jetpack compose", "compose"] },
|
|
3305
|
+
{ name: BACKEND_ONLY_FRAMEWORK, strategy: "cdn-template", aliases: [] }
|
|
2952
3306
|
];
|
|
2953
|
-
var
|
|
3307
|
+
var CURATED_FRAMEWORKS = FRAMEWORKS.map(
|
|
3308
|
+
(f) => f.name
|
|
3309
|
+
);
|
|
2954
3310
|
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2955
|
-
var
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
javascript: "vanillajs",
|
|
2969
|
-
js: "vanillajs"
|
|
2970
|
-
};
|
|
2971
|
-
var isSameFramework = (a, b) => {
|
|
2972
|
-
const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
|
|
2973
|
-
const y = FRAMEWORK_ALIASES[normalize(b)] ?? normalize(b);
|
|
3311
|
+
var ALIAS_TO_NAME = /* @__PURE__ */ new Map();
|
|
3312
|
+
for (const framework of FRAMEWORKS) {
|
|
3313
|
+
for (const alias of [framework.name, ...framework.aliases]) {
|
|
3314
|
+
ALIAS_TO_NAME.set(normalize(alias), framework.name);
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
var STRATEGY_BY_NAME = new Map(FRAMEWORKS.map((f) => [f.name, f.strategy]));
|
|
3318
|
+
function canonicalFrameworkName(name) {
|
|
3319
|
+
return ALIAS_TO_NAME.get(normalize(name));
|
|
3320
|
+
}
|
|
3321
|
+
function isSameFramework(a, b) {
|
|
3322
|
+
const x = canonicalFrameworkName(a) ?? normalize(a);
|
|
3323
|
+
const y = canonicalFrameworkName(b) ?? normalize(b);
|
|
2974
3324
|
return x !== "" && x === y;
|
|
2975
|
-
}
|
|
2976
|
-
function
|
|
3325
|
+
}
|
|
3326
|
+
function resolveSearchStrategy(frameworkName, hasJavaScriptInStack) {
|
|
3327
|
+
const canonical = frameworkName ? canonicalFrameworkName(frameworkName) : void 0;
|
|
3328
|
+
const strategy = canonical ? STRATEGY_BY_NAME.get(canonical) : void 0;
|
|
3329
|
+
if (strategy) return strategy;
|
|
3330
|
+
return hasJavaScriptInStack ? "js" : "cdn-template";
|
|
3331
|
+
}
|
|
3332
|
+
function searchDocKey(strategy) {
|
|
3333
|
+
return strategy === "cdn-template" ? "templates" : strategy;
|
|
3334
|
+
}
|
|
3335
|
+
function bundlesJavaScript(strategy) {
|
|
3336
|
+
return strategy !== "cdn-template" && strategy !== "none";
|
|
3337
|
+
}
|
|
3338
|
+
function canScaffoldSearchUI(strategy) {
|
|
3339
|
+
return strategy !== "none";
|
|
3340
|
+
}
|
|
3341
|
+
var ENV_PREFIXES = [
|
|
3342
|
+
{ aliases: ["next", "nextjs"], prefix: "NEXT_PUBLIC_" },
|
|
3343
|
+
{ aliases: ["nuxt", "nuxtjs"], prefix: "NUXT_PUBLIC_" },
|
|
3344
|
+
{ aliases: ["astro"], prefix: "PUBLIC_" },
|
|
3345
|
+
{ aliases: ["vite"], prefix: "VITE_" }
|
|
3346
|
+
];
|
|
3347
|
+
var DEFAULT_ENV_PREFIX = "PUBLIC_";
|
|
3348
|
+
function publicEnvPrefix(frameworkNames, strategy) {
|
|
3349
|
+
if (!bundlesJavaScript(strategy)) return "";
|
|
3350
|
+
const present = new Set(frameworkNames.map(normalize));
|
|
3351
|
+
for (const { aliases, prefix } of ENV_PREFIXES) {
|
|
3352
|
+
if (aliases.some((alias) => present.has(alias))) return prefix;
|
|
3353
|
+
}
|
|
3354
|
+
return DEFAULT_ENV_PREFIX;
|
|
3355
|
+
}
|
|
3356
|
+
function describeSearchTarget(strategy, frameworkName) {
|
|
3357
|
+
switch (strategy) {
|
|
3358
|
+
case "react":
|
|
3359
|
+
return "React (react-instantsearch)";
|
|
3360
|
+
case "vue":
|
|
3361
|
+
return "Vue (vue-instantsearch)";
|
|
3362
|
+
case "angular":
|
|
3363
|
+
return "Angular (angular-instantsearch)";
|
|
3364
|
+
case "js":
|
|
3365
|
+
return "plain JavaScript (InstantSearch.js)";
|
|
3366
|
+
case "cdn-template":
|
|
3367
|
+
return `${frameworkName ?? "server-rendered"} templates (InstantSearch.js via CDN)`;
|
|
3368
|
+
case "none":
|
|
3369
|
+
return frameworkName ?? "a native mobile app";
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
// src/actions/confirmFramework.ts
|
|
3374
|
+
var confirmFrameworkSchema = z20.object({
|
|
3375
|
+
frameworks: detectLanguageSchema.shape.frameworks
|
|
3376
|
+
});
|
|
3377
|
+
var OTHER_OPTION2 = "Other";
|
|
3378
|
+
function confirmed2(name, version) {
|
|
2977
3379
|
const frameworks = [{ name, version: version ?? "unknown" }];
|
|
2978
3380
|
track("AI Wizard Frontend Framework Confirmed", { frameworks });
|
|
2979
3381
|
return { frameworks };
|
|
@@ -3001,7 +3403,7 @@ async function confirmFramework(ctx) {
|
|
|
3001
3403
|
for (const fw of detectedFrameworks) {
|
|
3002
3404
|
if (!options.some((o) => isSameFramework(o, fw.name))) options.push(fw.name);
|
|
3003
3405
|
}
|
|
3004
|
-
options.push(
|
|
3406
|
+
options.push(OTHER_OPTION2);
|
|
3005
3407
|
const detectedFor = (option) => detectedFrameworks.find((fw) => isSameFramework(option, fw.name));
|
|
3006
3408
|
const primary = detectedFrameworks[0];
|
|
3007
3409
|
if (primary) {
|
|
@@ -3011,7 +3413,7 @@ async function confirmFramework(ctx) {
|
|
|
3011
3413
|
options: [`Confirm ${primary.name}`, "Use a different framework"],
|
|
3012
3414
|
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0]
|
|
3013
3415
|
});
|
|
3014
|
-
if (accepted === true) return
|
|
3416
|
+
if (accepted === true) return confirmed2(primary.name, primary.version);
|
|
3015
3417
|
}
|
|
3016
3418
|
const secondary = options.map(
|
|
3017
3419
|
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
@@ -3021,7 +3423,7 @@ async function confirmFramework(ctx) {
|
|
|
3021
3423
|
0
|
|
3022
3424
|
);
|
|
3023
3425
|
const selection = await ctx.requestUserInput({
|
|
3024
|
-
prompt: "select
|
|
3426
|
+
prompt: "select the framework that renders your UI",
|
|
3025
3427
|
promptType: "multipleChoice",
|
|
3026
3428
|
options,
|
|
3027
3429
|
secondary,
|
|
@@ -3030,10 +3432,10 @@ async function confirmFramework(ctx) {
|
|
|
3030
3432
|
if (typeof selection !== "string") {
|
|
3031
3433
|
throw new Error("confirmFramework received an unexpected non-text result");
|
|
3032
3434
|
}
|
|
3033
|
-
if (selection ===
|
|
3034
|
-
return
|
|
3435
|
+
if (selection === OTHER_OPTION2) {
|
|
3436
|
+
return confirmed2(await askOtherFramework(ctx));
|
|
3035
3437
|
}
|
|
3036
|
-
return
|
|
3438
|
+
return confirmed2(selection, detectedFor(selection)?.version);
|
|
3037
3439
|
}
|
|
3038
3440
|
|
|
3039
3441
|
// src/actions/promptUser.ts
|
|
@@ -3126,15 +3528,15 @@ async function confirmEntities(ctx) {
|
|
|
3126
3528
|
onSubmit: () => {
|
|
3127
3529
|
}
|
|
3128
3530
|
});
|
|
3129
|
-
const
|
|
3130
|
-
if (
|
|
3531
|
+
const confirmed3 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
|
|
3532
|
+
if (confirmed3.length === 0) {
|
|
3131
3533
|
throw new Error("User cancelled entity selection \u2014 analysis halted.");
|
|
3132
3534
|
}
|
|
3133
|
-
ctx.setUserInput("confirmedEntities",
|
|
3535
|
+
ctx.setUserInput("confirmedEntities", confirmed3);
|
|
3134
3536
|
track("AI Wizard Entities Confirmed", {
|
|
3135
|
-
entities: toEntitySummary(
|
|
3537
|
+
entities: toEntitySummary(confirmed3)
|
|
3136
3538
|
});
|
|
3137
|
-
return { ingestionAnalysis: entities, confirmedEntities:
|
|
3539
|
+
return { ingestionAnalysis: entities, confirmedEntities: confirmed3 };
|
|
3138
3540
|
}
|
|
3139
3541
|
|
|
3140
3542
|
// src/actions/review.ts
|
|
@@ -3158,7 +3560,7 @@ ${JSON.stringify(s.output, null, 2)}`
|
|
|
3158
3560
|
}
|
|
3159
3561
|
function formatReviewSummary(result) {
|
|
3160
3562
|
const nextStepLines = result.nextSteps.map((step) => {
|
|
3161
|
-
const isIngestCommand = step.includes("
|
|
3563
|
+
const isIngestCommand = step.includes("algolia-wizard/");
|
|
3162
3564
|
const isWorktreeCommand = step.includes("/worktrees/");
|
|
3163
3565
|
return {
|
|
3164
3566
|
text: `\u2192 ${step}`,
|
|
@@ -3200,13 +3602,14 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3200
3602
|
import z24 from "zod";
|
|
3201
3603
|
|
|
3202
3604
|
// src/lib/worktree.ts
|
|
3203
|
-
import { execFile
|
|
3204
|
-
import {
|
|
3605
|
+
import { execFile } from "node:child_process";
|
|
3606
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
3607
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir4, readFile as readFile9, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3205
3608
|
import {
|
|
3206
3609
|
basename as basename2,
|
|
3207
3610
|
dirname as dirname7,
|
|
3208
3611
|
isAbsolute as isAbsolute2,
|
|
3209
|
-
join as
|
|
3612
|
+
join as join12,
|
|
3210
3613
|
relative as relative2,
|
|
3211
3614
|
resolve as resolve3
|
|
3212
3615
|
} from "node:path";
|
|
@@ -3240,8 +3643,8 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
3240
3643
|
return out.trim().length > 0;
|
|
3241
3644
|
}
|
|
3242
3645
|
async function pruneOldWorktrees(repoRoot) {
|
|
3243
|
-
const dir =
|
|
3244
|
-
const stale = (await
|
|
3646
|
+
const dir = join12(stateDir(repoRoot), "worktrees");
|
|
3647
|
+
const stale = (await readdir4(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
3245
3648
|
for (const slug of stale) {
|
|
3246
3649
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
3247
3650
|
try {
|
|
@@ -3251,7 +3654,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3251
3654
|
"worktree",
|
|
3252
3655
|
"remove",
|
|
3253
3656
|
"--force",
|
|
3254
|
-
|
|
3657
|
+
join12(dir, slug)
|
|
3255
3658
|
]);
|
|
3256
3659
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
3257
3660
|
} catch (err) {
|
|
@@ -3265,43 +3668,55 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3265
3668
|
async function createWorktree(repoRoot) {
|
|
3266
3669
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
3267
3670
|
const dirSlug = branch.replace(/\//g, "-");
|
|
3268
|
-
const path =
|
|
3671
|
+
const path = join12(stateDir(repoRoot), "worktrees", dirSlug);
|
|
3269
3672
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
3270
3673
|
await pruneOldWorktrees(repoRoot);
|
|
3271
3674
|
await mkdir6(dirname7(path), { recursive: true });
|
|
3272
3675
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
3273
3676
|
return { path, branch };
|
|
3274
3677
|
}
|
|
3275
|
-
async function
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
return { ok: true, output: "no package.json; skipped install" };
|
|
3280
|
-
}
|
|
3281
|
-
const pm = await detectPackageManager(worktreePath);
|
|
3282
|
-
return new Promise((resolve4) => {
|
|
3283
|
-
let output = "";
|
|
3284
|
-
const child = spawn3(pm, ["install"], {
|
|
3285
|
-
cwd: worktreePath,
|
|
3286
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3287
|
-
});
|
|
3288
|
-
child.stdout?.on("data", (d) => output += d);
|
|
3289
|
-
child.stderr?.on("data", (d) => output += d);
|
|
3290
|
-
child.on(
|
|
3291
|
-
"error",
|
|
3292
|
-
(err) => resolve4({
|
|
3293
|
-
ok: false,
|
|
3294
|
-
output: `Failed to run ${pm} install: ${err.message}`
|
|
3295
|
-
})
|
|
3296
|
-
);
|
|
3297
|
-
child.on(
|
|
3298
|
-
"close",
|
|
3299
|
-
(code) => resolve4({ ok: code === 0, output: output.trim() })
|
|
3300
|
-
);
|
|
3678
|
+
async function spawnStep(worktreePath, argv) {
|
|
3679
|
+
const { code, output } = await runCommand(argv[0], [...argv.slice(1)], {
|
|
3680
|
+
cwd: worktreePath,
|
|
3681
|
+
timeoutMs: INSTALL_TIMEOUT_MS
|
|
3301
3682
|
});
|
|
3683
|
+
return { ok: code === 0, output: output.trim() };
|
|
3684
|
+
}
|
|
3685
|
+
async function installWorktreeDeps(worktreePath, toolchain) {
|
|
3686
|
+
const { profile, installSteps, packageManager } = toolchain;
|
|
3687
|
+
const declared = packageManager.dependency.mode === "agent-declares" ? packageManager.dependency.file : void 0;
|
|
3688
|
+
const haveSomethingToInstall = await hasProfileManifest(worktreePath, profile) || declared !== void 0 && existsSync4(join12(worktreePath, declared));
|
|
3689
|
+
if (!haveSomethingToInstall) {
|
|
3690
|
+
return {
|
|
3691
|
+
ok: true,
|
|
3692
|
+
output: `no ${profile.displayName} manifest; skipped install`
|
|
3693
|
+
};
|
|
3694
|
+
}
|
|
3695
|
+
if (installSteps.length === 0) {
|
|
3696
|
+
return {
|
|
3697
|
+
ok: true,
|
|
3698
|
+
output: `${profile.displayName} (${toolchain.packageManager.id}) has no wizard-run install step`
|
|
3699
|
+
};
|
|
3700
|
+
}
|
|
3701
|
+
const outputs = [];
|
|
3702
|
+
for (const step of installSteps) {
|
|
3703
|
+
if (step.requiresFile && !existsSync4(join12(worktreePath, step.requiresFile)))
|
|
3704
|
+
continue;
|
|
3705
|
+
const result = await spawnStep(worktreePath, step.argv);
|
|
3706
|
+
if (result.output) outputs.push(result.output);
|
|
3707
|
+
if (result.ok) continue;
|
|
3708
|
+
if (step.optional) {
|
|
3709
|
+
logger.warn(
|
|
3710
|
+
{ step: step.argv.join(" "), output: result.output },
|
|
3711
|
+
"installWorktreeDeps: optional install step failed; continuing"
|
|
3712
|
+
);
|
|
3713
|
+
continue;
|
|
3714
|
+
}
|
|
3715
|
+
return { ok: false, output: outputs.join("\n").trim() };
|
|
3716
|
+
}
|
|
3717
|
+
return { ok: true, output: outputs.join("\n").trim() };
|
|
3302
3718
|
}
|
|
3303
|
-
|
|
3304
|
-
function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
3719
|
+
function validateIngestEntrypoint(worktreePath, entrypoint, allowedExtensions) {
|
|
3305
3720
|
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
3306
3721
|
return {
|
|
3307
3722
|
ok: false,
|
|
@@ -3316,18 +3731,29 @@ function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
|
3316
3731
|
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
3317
3732
|
};
|
|
3318
3733
|
}
|
|
3734
|
+
if (allowedExtensions?.length && !allowedExtensions.some((ext) => entrypoint.endsWith(ext))) {
|
|
3735
|
+
return {
|
|
3736
|
+
ok: false,
|
|
3737
|
+
reason: `entrypoint "${entrypoint}" is not one of ${allowedExtensions.join(", ")}`
|
|
3738
|
+
};
|
|
3739
|
+
}
|
|
3319
3740
|
return { ok: true, target };
|
|
3320
3741
|
}
|
|
3321
|
-
async function runIngestScript(worktreePath,
|
|
3322
|
-
|
|
3742
|
+
async function runIngestScript(worktreePath, toolchain, entrypoint, env = {}) {
|
|
3743
|
+
const { ingest, profile, packageManager } = toolchain;
|
|
3744
|
+
if (ingest.kind !== "auto") {
|
|
3323
3745
|
return {
|
|
3324
3746
|
ran: false,
|
|
3325
3747
|
ok: false,
|
|
3326
3748
|
output: "",
|
|
3327
|
-
reason:
|
|
3749
|
+
reason: `${profile.displayName} (${packageManager.id}) projects must be run manually: ${ingest.runCommand}`
|
|
3328
3750
|
};
|
|
3329
3751
|
}
|
|
3330
|
-
const validated = validateIngestEntrypoint(
|
|
3752
|
+
const validated = validateIngestEntrypoint(
|
|
3753
|
+
worktreePath,
|
|
3754
|
+
entrypoint,
|
|
3755
|
+
ingest.entrypointExtensions
|
|
3756
|
+
);
|
|
3331
3757
|
if (!validated.ok) {
|
|
3332
3758
|
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
3333
3759
|
}
|
|
@@ -3348,29 +3774,13 @@ async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
|
3348
3774
|
reason: `entrypoint "${entrypoint}" does not exist`
|
|
3349
3775
|
};
|
|
3350
3776
|
}
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
3357
|
-
env: { ...process.env, ...env }
|
|
3358
|
-
});
|
|
3359
|
-
child.stdout?.on("data", (d) => output += d);
|
|
3360
|
-
child.stderr?.on("data", (d) => output += d);
|
|
3361
|
-
child.on(
|
|
3362
|
-
"error",
|
|
3363
|
-
(err) => resolveRun({
|
|
3364
|
-
ran: true,
|
|
3365
|
-
ok: false,
|
|
3366
|
-
output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
|
|
3367
|
-
})
|
|
3368
|
-
);
|
|
3369
|
-
child.on(
|
|
3370
|
-
"close",
|
|
3371
|
-
(code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
|
|
3372
|
-
);
|
|
3777
|
+
const argv = resolveIngestArgv(ingest, entrypoint);
|
|
3778
|
+
const { code, output } = await runCommand(argv[0], argv.slice(1), {
|
|
3779
|
+
cwd: worktreePath,
|
|
3780
|
+
env,
|
|
3781
|
+
timeoutMs: INGEST_TIMEOUT_MS
|
|
3373
3782
|
});
|
|
3783
|
+
return { ran: true, ok: code === 0, output: output.trim() };
|
|
3374
3784
|
}
|
|
3375
3785
|
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
3376
3786
|
const trimmed = sourcePath.trim();
|
|
@@ -3385,8 +3795,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3385
3795
|
} catch {
|
|
3386
3796
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
3387
3797
|
}
|
|
3388
|
-
const relPath =
|
|
3389
|
-
const dest =
|
|
3798
|
+
const relPath = join12(ingestDir, basename2(source));
|
|
3799
|
+
const dest = join12(worktreePath, relPath);
|
|
3390
3800
|
try {
|
|
3391
3801
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
3392
3802
|
await copyFile(source, dest);
|
|
@@ -3402,10 +3812,10 @@ function hasEnvVar(content, name) {
|
|
|
3402
3812
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
3403
3813
|
}
|
|
3404
3814
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
3405
|
-
const target =
|
|
3815
|
+
const target = join12(worktreePath, ".env");
|
|
3406
3816
|
let existing = "";
|
|
3407
3817
|
try {
|
|
3408
|
-
existing = await
|
|
3818
|
+
existing = await readFile9(target, "utf8");
|
|
3409
3819
|
} catch (err) {
|
|
3410
3820
|
if (err.code !== "ENOENT") throw err;
|
|
3411
3821
|
}
|
|
@@ -3522,69 +3932,33 @@ async function resolveSearchOnlyKey(index) {
|
|
|
3522
3932
|
}
|
|
3523
3933
|
|
|
3524
3934
|
// src/lib/algoliaDocs.ts
|
|
3525
|
-
import { readFileSync,
|
|
3526
|
-
import { dirname as dirname8, join as
|
|
3935
|
+
import { readFileSync, existsSync as existsSync5 } from "node:fs";
|
|
3936
|
+
import { dirname as dirname8, join as join13 } from "node:path";
|
|
3527
3937
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3528
|
-
var DOCS_SUBPATH =
|
|
3938
|
+
var DOCS_SUBPATH = join13("docs", "algolia-sdk");
|
|
3529
3939
|
function findDocsDir() {
|
|
3530
3940
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
3531
3941
|
for (; ; ) {
|
|
3532
|
-
const candidate =
|
|
3533
|
-
if (
|
|
3942
|
+
const candidate = join13(dir, DOCS_SUBPATH);
|
|
3943
|
+
if (existsSync5(candidate)) return candidate;
|
|
3534
3944
|
const parent = dirname8(dir);
|
|
3535
3945
|
if (parent === dir) return void 0;
|
|
3536
3946
|
dir = parent;
|
|
3537
3947
|
}
|
|
3538
3948
|
}
|
|
3539
|
-
function
|
|
3540
|
-
const docsDir = findDocsDir();
|
|
3541
|
-
if (!docsDir) {
|
|
3542
|
-
logger.warn(
|
|
3543
|
-
"algoliaDocs: docs/algolia-sdk not found; skipping SDK reference"
|
|
3544
|
-
);
|
|
3545
|
-
return "";
|
|
3546
|
-
}
|
|
3547
|
-
const files = readdirSync(docsDir).filter((f) => f.includes(language));
|
|
3548
|
-
if (files.length === 0) {
|
|
3549
|
-
logger.warn(
|
|
3550
|
-
{ language },
|
|
3551
|
-
"algoliaDocs: no SDK reference found for language; skipping"
|
|
3552
|
-
);
|
|
3553
|
-
return "";
|
|
3554
|
-
}
|
|
3555
|
-
return readFileSync(join11(docsDir, files[0]), "utf8").trim();
|
|
3556
|
-
}
|
|
3557
|
-
function getNamedDoc(name, language) {
|
|
3949
|
+
function getNamedDoc(name, key) {
|
|
3558
3950
|
const docsDir = findDocsDir();
|
|
3559
3951
|
if (!docsDir) {
|
|
3560
3952
|
logger.warn("docs/algolia-sdk not found");
|
|
3561
3953
|
return "";
|
|
3562
3954
|
}
|
|
3563
|
-
const file =
|
|
3564
|
-
if (!
|
|
3565
|
-
logger.warn({ name,
|
|
3955
|
+
const file = join13(docsDir, `${name}-${key}.md`);
|
|
3956
|
+
if (!existsSync5(file)) {
|
|
3957
|
+
logger.warn({ name, key }, "named SDK reference not found");
|
|
3566
3958
|
return "";
|
|
3567
3959
|
}
|
|
3568
3960
|
return readFileSync(file, "utf8").trim();
|
|
3569
3961
|
}
|
|
3570
|
-
function getFrameworkSpecificDoc(frameworks) {
|
|
3571
|
-
const fw = frameworks.map((f) => f.toLowerCase());
|
|
3572
|
-
if (fw.includes("vue") || fw.includes("nuxt")) {
|
|
3573
|
-
return loadAlgoliaDoc("vue");
|
|
3574
|
-
}
|
|
3575
|
-
if (fw.includes("react") || fw.includes("next.js")) {
|
|
3576
|
-
return loadAlgoliaDoc("react");
|
|
3577
|
-
}
|
|
3578
|
-
if (fw.includes("angular")) {
|
|
3579
|
-
return loadAlgoliaDoc("angular");
|
|
3580
|
-
}
|
|
3581
|
-
return loadAlgoliaDoc("js");
|
|
3582
|
-
}
|
|
3583
|
-
|
|
3584
|
-
// src/lib/shell.ts
|
|
3585
|
-
function shellQuote(value) {
|
|
3586
|
-
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
3587
|
-
}
|
|
3588
3962
|
|
|
3589
3963
|
// src/actions/implement.ts
|
|
3590
3964
|
var implementSchema = z24.object({
|
|
@@ -3619,12 +3993,11 @@ var implementSchema = z24.object({
|
|
|
3619
3993
|
});
|
|
3620
3994
|
var implementationOutputSchema = z24.object({
|
|
3621
3995
|
summary: z24.string(),
|
|
3622
|
-
// Ingestion only:
|
|
3623
|
-
//
|
|
3624
|
-
//
|
|
3625
|
-
//
|
|
3626
|
-
// the agent
|
|
3627
|
-
runtime: z24.enum(INGEST_RUNTIMES).optional(),
|
|
3996
|
+
// Ingestion only: the script the wizard should run, as a bare path — never a
|
|
3997
|
+
// command string, and never the interpreter. The command comes from the
|
|
3998
|
+
// resolved language toolchain (a registry constant); this path is validated to
|
|
3999
|
+
// a worktree-relative file with a runnable extension and substituted into it.
|
|
4000
|
+
// So the agent contributes no part of the command that gets executed.
|
|
3628
4001
|
entrypoint: z24.string().optional()
|
|
3629
4002
|
});
|
|
3630
4003
|
var verificationOutputSchema = z24.object({
|
|
@@ -3634,47 +4007,11 @@ var verificationOutputSchema = z24.object({
|
|
|
3634
4007
|
});
|
|
3635
4008
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
3636
4009
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
if (names.some((n) => n.includes("react") || n.includes("next")))
|
|
3642
|
-
return "React";
|
|
3643
|
-
if (names.some((n) => n.includes("angular"))) return "Angular";
|
|
3644
|
-
return "JavaScript";
|
|
3645
|
-
}
|
|
3646
|
-
function frameworksForDoc(framework) {
|
|
3647
|
-
switch (framework) {
|
|
3648
|
-
case "React":
|
|
3649
|
-
return ["react"];
|
|
3650
|
-
case "Vue":
|
|
3651
|
-
return ["vue"];
|
|
3652
|
-
case "Angular":
|
|
3653
|
-
return ["angular"];
|
|
3654
|
-
case "JavaScript":
|
|
3655
|
-
return [];
|
|
3656
|
-
}
|
|
3657
|
-
}
|
|
3658
|
-
function publicEnvPrefix(language) {
|
|
3659
|
-
const frameworkNames = language.frameworks.map(
|
|
3660
|
-
(framework) => framework.name.toLowerCase()
|
|
4010
|
+
function buildSearchEnvVars(language, strategy, appId, searchKey) {
|
|
4011
|
+
const prefix = publicEnvPrefix(
|
|
4012
|
+
language.frameworks.map((framework) => framework.name),
|
|
4013
|
+
strategy
|
|
3661
4014
|
);
|
|
3662
|
-
if (frameworkNames.some((name) => name.includes("next"))) {
|
|
3663
|
-
return "NEXT_PUBLIC_";
|
|
3664
|
-
}
|
|
3665
|
-
if (frameworkNames.some((name) => name.includes("nuxt"))) {
|
|
3666
|
-
return "NUXT_PUBLIC_";
|
|
3667
|
-
}
|
|
3668
|
-
if (frameworkNames.some((name) => name.includes("astro"))) {
|
|
3669
|
-
return "PUBLIC_";
|
|
3670
|
-
}
|
|
3671
|
-
if (frameworkNames.some((name) => name.includes("vite"))) {
|
|
3672
|
-
return "VITE_";
|
|
3673
|
-
}
|
|
3674
|
-
return "PUBLIC_";
|
|
3675
|
-
}
|
|
3676
|
-
function searchEnvVars(language, appId, searchKey) {
|
|
3677
|
-
const prefix = publicEnvPrefix(language);
|
|
3678
4015
|
return [
|
|
3679
4016
|
{
|
|
3680
4017
|
name: `${prefix}ALGOLIA_APP_ID`,
|
|
@@ -3686,6 +4023,38 @@ function searchEnvVars(language, appId, searchKey) {
|
|
|
3686
4023
|
}
|
|
3687
4024
|
];
|
|
3688
4025
|
}
|
|
4026
|
+
async function resolveIngestionProfile(ctx, language, repoRoot) {
|
|
4027
|
+
const { candidates, confirmed: confirmed3, onDisk } = await pickIngestionCandidates(
|
|
4028
|
+
repoRoot,
|
|
4029
|
+
language.languages.map((l) => l.name)
|
|
4030
|
+
);
|
|
4031
|
+
if (candidates.length === 0) {
|
|
4032
|
+
const chosen = confirmed3[0] ?? onDisk[0] ?? LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID];
|
|
4033
|
+
logger.warn(
|
|
4034
|
+
{
|
|
4035
|
+
confirmed: language.languages.map((l) => l.name),
|
|
4036
|
+
onDisk: onDisk.map((p) => p.id),
|
|
4037
|
+
chosen: chosen.id
|
|
4038
|
+
},
|
|
4039
|
+
"implement: no confirmed language matched a manifest on disk; falling back"
|
|
4040
|
+
);
|
|
4041
|
+
return chosen;
|
|
4042
|
+
}
|
|
4043
|
+
if (candidates.length === 1) return candidates[0];
|
|
4044
|
+
const backends = candidates.filter(isBackendLanguage);
|
|
4045
|
+
if (backends.length === 1) return backends[0];
|
|
4046
|
+
if (backends.length === 0) return candidates[0];
|
|
4047
|
+
if (isBackendLanguage(candidates[0])) return candidates[0];
|
|
4048
|
+
const options = backends.map((p) => p.displayName);
|
|
4049
|
+
const selection = await ctx.requestUserInput({
|
|
4050
|
+
prompt: "Which language should the ingestion script use?",
|
|
4051
|
+
promptType: "multipleChoice",
|
|
4052
|
+
options,
|
|
4053
|
+
defaultSelectedIndex: 0
|
|
4054
|
+
});
|
|
4055
|
+
const picked = typeof selection === "string" ? backends.find((p) => p.displayName === selection) : void 0;
|
|
4056
|
+
return picked ?? backends[0];
|
|
4057
|
+
}
|
|
3689
4058
|
function baseInstructions(input) {
|
|
3690
4059
|
return [
|
|
3691
4060
|
`Target Algolia index: ${input.targetIndex}`,
|
|
@@ -3713,37 +4082,48 @@ function sourceSpecificInstructions(input) {
|
|
|
3713
4082
|
generated: [
|
|
3714
4083
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
3715
4084
|
"Call the generateRecord tool once per entity (entityName, attributes, count 20-50); it invents the values and unique objectIDs and writes them to a JSON file in the worktree, returning the file path. Do not write records or objectIDs yourself.",
|
|
3716
|
-
"In the script, read and parse each returned file path at runtime
|
|
4085
|
+
"In the script, read and parse each returned JSON file path at runtime using the idiomatic file read for the language you are writing in (the SDK reference above shows one) instead of inlining the records as literals.",
|
|
3717
4086
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
3718
4087
|
]
|
|
3719
4088
|
};
|
|
3720
4089
|
return byLine[input.ingestionSource];
|
|
3721
4090
|
}
|
|
3722
4091
|
function ingestionInstructions(input) {
|
|
4092
|
+
const { ingestionProfile: profile, toolchain } = input;
|
|
4093
|
+
const { ingest } = toolchain;
|
|
4094
|
+
const extensions = ingest.entrypointExtensions.join(", ");
|
|
4095
|
+
const runInstruction = ingest.kind === "auto" ? `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard runs it with \`${describeIngestCommand(ingest, profile.ingestEntrypointExample)}\`, so it must be a plain path with no flags or arguments and must run as-is under that command.` : `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard does NOT run ${profile.displayName} ${toolchain.packageManager.id} projects itself \u2014 it tells the developer to run \`${describeIngestCommand(ingest, profile.ingestEntrypointExample)}\`, so also add whatever build configuration that command needs${ingest.kind === "manual" && ingest.requiresBuildTask ? `, including a "${ingest.requiresBuildTask}" task in "${toolchain.packageManager.dependency.mode === "agent-declares" ? toolchain.packageManager.dependency.file : "the build file"}" that runs the script` : ""}.`;
|
|
3723
4096
|
return [
|
|
3724
4097
|
...input.confirmed && input.confirmed.length ? [
|
|
3725
|
-
`
|
|
4098
|
+
`Write the ingestion script in ${profile.displayName}, at "${ingestScriptDir(profile)}/" in the repo.`,
|
|
3726
4099
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
3727
|
-
`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.`,
|
|
3728
|
-
|
|
4100
|
+
`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. ${profile.envReadInstruction} The wizard sets these when it runs the script.`,
|
|
4101
|
+
`Use the official Algolia ${profile.displayName} client (${profile.sdk.packageName}). Do not use the raw HTTP API, and do not use a client for another language.`,
|
|
3729
4102
|
"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.",
|
|
3730
|
-
getNamedDoc("save-records",
|
|
3731
|
-
|
|
4103
|
+
getNamedDoc("save-records", profile.sdk.docKey),
|
|
4104
|
+
dependencyInstruction(toolchain),
|
|
3732
4105
|
"The summary should be extremely concise.",
|
|
3733
|
-
|
|
4106
|
+
runInstruction,
|
|
3734
4107
|
...sourceSpecificInstructions(input)
|
|
3735
4108
|
] : []
|
|
3736
4109
|
];
|
|
3737
4110
|
}
|
|
3738
4111
|
function searchInstructions(input) {
|
|
3739
|
-
const doc =
|
|
4112
|
+
const doc = getNamedDoc(
|
|
4113
|
+
"instantsearch-setup",
|
|
4114
|
+
searchDocKey(input.searchStrategy)
|
|
4115
|
+
);
|
|
4116
|
+
const isTemplate = input.searchStrategy === "cdn-template";
|
|
4117
|
+
const placement = isTemplate ? input.searchLocation ? `Add the search UI to the server-rendered template at "${input.searchLocation}" \u2014 ideally a shared layout, so it is reachable across the app.` : `This project has no shared template to host the UI, so create a standalone page at "${input.ingestDir}/search-demo.html" the developer can open directly, and add a TODO explaining how to move the snippet into their own layout.` : `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.`;
|
|
3740
4118
|
return [
|
|
3741
4119
|
"Implement an in-app Algolia search experience.",
|
|
3742
|
-
`Build the search UI for ${input.
|
|
3743
|
-
"Follow the Algolia
|
|
4120
|
+
`Build the search UI for ${describeSearchTarget(input.searchStrategy, input.frameworkName)}.`,
|
|
4121
|
+
"Follow the Algolia reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3744
4122
|
doc,
|
|
3745
|
-
|
|
3746
|
-
|
|
4123
|
+
placement,
|
|
4124
|
+
`It needs at least a working SearchBox and Hits against the "${input.targetIndex}" index.`,
|
|
4125
|
+
isTemplate ? "Load InstantSearch from a CDN with script tags as shown in the reference. Do not add JavaScript package dependencies, a bundler, or a build step." : '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.',
|
|
4126
|
+
isTemplate ? "Read the App ID and search-only API key from server-side configuration/environment and render them into the page (e.g. as data- attributes the script reads); never hardcode them, and never put a write/admin key in HTML." : "Read the App ID and a search-only API key from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
3747
4127
|
// appId always resolves (loadActiveProfile throws otherwise); only the
|
|
3748
4128
|
// search-only key is best-effort and can fall back to a placeholder.
|
|
3749
4129
|
`Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
@@ -3751,20 +4131,22 @@ function searchInstructions(input) {
|
|
|
3751
4131
|
// resolved app id / search-only key into ".env" under these exact names
|
|
3752
4132
|
// right after this step, so a renamed prefix here would leave the code
|
|
3753
4133
|
// reading a var the wizard never wrote.
|
|
3754
|
-
`Use exactly these
|
|
3755
|
-
'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.',
|
|
4134
|
+
`Use exactly these env var names: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3756
4135
|
"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."
|
|
3757
4136
|
];
|
|
3758
4137
|
}
|
|
3759
4138
|
function verificationInstructions(input) {
|
|
4139
|
+
const protectedDirs = [
|
|
4140
|
+
.../* @__PURE__ */ new Set([input.ingestDir, ingestScriptDir(input.ingestionProfile)])
|
|
4141
|
+
];
|
|
3760
4142
|
return [
|
|
3761
4143
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
3762
4144
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
3763
|
-
|
|
4145
|
+
`Call verifyImplementation at least once; it runs the mechanical checks available for this repo's languages (${input.verificationLanguages.join(", ")}) and returns per-check results plus an aggregate ok.`,
|
|
3764
4146
|
"For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
|
|
3765
4147
|
"Do not make speculative fixes when verifyImplementation cannot run, no checks exist, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
|
|
3766
4148
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
3767
|
-
`Do not modify "${
|
|
4149
|
+
`Do not modify ${protectedDirs.map((dir) => `"${dir}/"`).join(" or ")} unless verifyImplementation reports an actionable issue in its files.`,
|
|
3768
4150
|
"Always call reportStatus with status=success once verification has run, even when sufficient=false.",
|
|
3769
4151
|
"Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
|
|
3770
4152
|
"Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
|
|
@@ -3773,14 +4155,17 @@ function verificationInstructions(input) {
|
|
|
3773
4155
|
var IMPLEMENT_CONFIG = {
|
|
3774
4156
|
ingestion: {
|
|
3775
4157
|
title: "Algolia ingestion",
|
|
4158
|
+
label: "Ingestion",
|
|
3776
4159
|
buildInstructions: ingestionInstructions
|
|
3777
4160
|
},
|
|
3778
4161
|
search: {
|
|
3779
4162
|
title: "Algolia search",
|
|
4163
|
+
label: "Search",
|
|
3780
4164
|
buildInstructions: searchInstructions
|
|
3781
4165
|
},
|
|
3782
4166
|
verification: {
|
|
3783
4167
|
title: "Algolia verification",
|
|
4168
|
+
label: "Verification",
|
|
3784
4169
|
buildInstructions: verificationInstructions
|
|
3785
4170
|
}
|
|
3786
4171
|
};
|
|
@@ -3812,11 +4197,10 @@ function buildAgentInstructions(useCase, input, extraInstructions = []) {
|
|
|
3812
4197
|
];
|
|
3813
4198
|
}
|
|
3814
4199
|
function formatSummary(useCase, summary) {
|
|
3815
|
-
|
|
3816
|
-
return `${label}: ${summary}`;
|
|
4200
|
+
return `${IMPLEMENT_CONFIG[useCase].label}: ${summary}`;
|
|
3817
4201
|
}
|
|
3818
|
-
function buildIngestCommand(worktree,
|
|
3819
|
-
return `cd ${shellQuote(worktree)} && ${
|
|
4202
|
+
function buildIngestCommand(worktree, toolchain, entrypoint) {
|
|
4203
|
+
return `cd ${shellQuote(worktree)} && ${describeIngestCommand(toolchain.ingest, entrypoint)}`;
|
|
3820
4204
|
}
|
|
3821
4205
|
function parseIngestRecordCount(output) {
|
|
3822
4206
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
@@ -3898,7 +4282,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3898
4282
|
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
3899
4283
|
}
|
|
3900
4284
|
const normalized = normalizeFindingPaths(findings);
|
|
3901
|
-
const
|
|
4285
|
+
const confirmed3 = normalized.confirmedEntities;
|
|
3902
4286
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
3903
4287
|
let appId;
|
|
3904
4288
|
let searchKey;
|
|
@@ -3936,31 +4320,66 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3936
4320
|
);
|
|
3937
4321
|
}
|
|
3938
4322
|
}
|
|
4323
|
+
const ingestionProfile = await resolveIngestionProfile(
|
|
4324
|
+
ctx,
|
|
4325
|
+
language,
|
|
4326
|
+
worktree
|
|
4327
|
+
);
|
|
4328
|
+
const toolchain = await resolveToolchain(worktree, ingestionProfile);
|
|
4329
|
+
const verificationLanguages = [
|
|
4330
|
+
.../* @__PURE__ */ new Set([
|
|
4331
|
+
ingestionProfile.id,
|
|
4332
|
+
...(await detectProfilesFromManifests(worktree)).map((p) => p.id)
|
|
4333
|
+
])
|
|
4334
|
+
];
|
|
4335
|
+
const frameworkName = language.frameworks[0]?.name;
|
|
4336
|
+
const searchStrategy = resolveSearchStrategy(
|
|
4337
|
+
frameworkName,
|
|
4338
|
+
verificationLanguages.includes(JAVASCRIPT)
|
|
4339
|
+
);
|
|
4340
|
+
logger.info(
|
|
4341
|
+
{
|
|
4342
|
+
language: ingestionProfile.id,
|
|
4343
|
+
packageManager: toolchain.packageManager.id,
|
|
4344
|
+
ingest: toolchain.ingest.kind,
|
|
4345
|
+
framework: frameworkName,
|
|
4346
|
+
searchStrategy
|
|
4347
|
+
},
|
|
4348
|
+
"implement: resolved ingestion toolchain and search strategy"
|
|
4349
|
+
);
|
|
3939
4350
|
const input = {
|
|
3940
4351
|
findings: normalized,
|
|
3941
|
-
confirmed:
|
|
4352
|
+
confirmed: confirmed3,
|
|
3942
4353
|
searchLocation,
|
|
3943
4354
|
targetIndex,
|
|
3944
4355
|
language,
|
|
3945
4356
|
appId,
|
|
3946
4357
|
searchKey,
|
|
3947
|
-
searchEnvVars:
|
|
4358
|
+
searchEnvVars: buildSearchEnvVars(
|
|
4359
|
+
language,
|
|
4360
|
+
searchStrategy,
|
|
4361
|
+
appId,
|
|
4362
|
+
searchKey
|
|
4363
|
+
),
|
|
3948
4364
|
ingestDir: INGEST_DIR,
|
|
3949
4365
|
ingestionSource,
|
|
3950
4366
|
uploadFilePath,
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
4367
|
+
searchStrategy,
|
|
4368
|
+
frameworkName,
|
|
4369
|
+
ingestionProfile,
|
|
4370
|
+
toolchain,
|
|
4371
|
+
verificationLanguages
|
|
3954
4372
|
};
|
|
4373
|
+
const searchToolchain = !bundlesJavaScript(searchStrategy) ? void 0 : ingestionProfile.id === JAVASCRIPT ? toolchain : await resolveToolchain(worktree, LANGUAGE_PROFILES[JAVASCRIPT]);
|
|
4374
|
+
const toolchainForUseCase = (useCase) => useCase === "search" ? searchToolchain : toolchain;
|
|
3955
4375
|
const summaries = [];
|
|
3956
4376
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
3957
4377
|
let agentRuns = 0;
|
|
3958
|
-
let ingestRuntime;
|
|
3959
4378
|
let ingestEntrypoint;
|
|
3960
4379
|
let ingestScriptRan = false;
|
|
3961
4380
|
let ingestRecordCount;
|
|
3962
4381
|
let ingestDurationMs;
|
|
3963
|
-
|
|
4382
|
+
const failedInstalls = /* @__PURE__ */ new Set();
|
|
3964
4383
|
let ingestOutcomeMessage;
|
|
3965
4384
|
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
3966
4385
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
@@ -3974,16 +4393,19 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3974
4393
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
3975
4394
|
outputSchema: implementationOutputSchema
|
|
3976
4395
|
});
|
|
4396
|
+
const useCaseToolchain = toolchainForUseCase(currentUseCase);
|
|
4397
|
+
if (!useCaseToolchain) return result;
|
|
3977
4398
|
ctx.notify({
|
|
3978
4399
|
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
3979
4400
|
});
|
|
3980
4401
|
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
3981
|
-
useCase: currentUseCase
|
|
4402
|
+
useCase: currentUseCase,
|
|
4403
|
+
language: useCaseToolchain.profile.id
|
|
3982
4404
|
});
|
|
3983
|
-
const install = await installWorktreeDeps(worktree);
|
|
4405
|
+
const install = await installWorktreeDeps(worktree, useCaseToolchain);
|
|
3984
4406
|
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
3985
4407
|
if (!install.ok) {
|
|
3986
|
-
|
|
4408
|
+
failedInstalls.add(useCaseToolchain.profile.displayName);
|
|
3987
4409
|
logger.warn(
|
|
3988
4410
|
{ useCase: currentUseCase, output: install.output },
|
|
3989
4411
|
"implement: dependency install in worktree failed; generated commands may not run until deps are installed"
|
|
@@ -3997,15 +4419,16 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3997
4419
|
return runAgent({
|
|
3998
4420
|
instructions: buildAgentInstructions("verification", input),
|
|
3999
4421
|
tools: toolsForUseCase("verification"),
|
|
4000
|
-
outputSchema: verificationOutputSchema
|
|
4422
|
+
outputSchema: verificationOutputSchema,
|
|
4423
|
+
// So verifyImplementation runs this repo's checks, not just npm scripts.
|
|
4424
|
+
languages: input.verificationLanguages
|
|
4001
4425
|
});
|
|
4002
4426
|
}
|
|
4003
4427
|
if (useCases.includes("ingestion")) {
|
|
4004
|
-
const { summary,
|
|
4428
|
+
const { summary, entrypoint } = await runImplementationUseCase("ingestion");
|
|
4005
4429
|
summaries.push(formatSummary("ingestion", summary));
|
|
4006
|
-
ingestRuntime = runtime;
|
|
4007
4430
|
ingestEntrypoint = entrypoint;
|
|
4008
|
-
if (
|
|
4431
|
+
if (ingestEntrypoint && toolchain.ingest.kind === "auto" && failedInstalls.size === 0) {
|
|
4009
4432
|
ctx.clearNotices();
|
|
4010
4433
|
const runNow = await ctx.requestUserInput({
|
|
4011
4434
|
prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
|
|
@@ -4017,13 +4440,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4017
4440
|
const profile = await loadActiveProfile();
|
|
4018
4441
|
ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
|
|
4019
4442
|
const scriptLogId = ctx.logStart("runIngestScript", {
|
|
4020
|
-
|
|
4443
|
+
language: ingestionProfile.id,
|
|
4021
4444
|
entrypoint: ingestEntrypoint
|
|
4022
4445
|
});
|
|
4023
4446
|
const startedAt = Date.now();
|
|
4024
4447
|
const run2 = await runIngestScript(
|
|
4025
4448
|
worktree,
|
|
4026
|
-
|
|
4449
|
+
toolchain,
|
|
4027
4450
|
ingestEntrypoint,
|
|
4028
4451
|
{
|
|
4029
4452
|
[APP_ID_VAR]: profile.appId,
|
|
@@ -4037,7 +4460,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4037
4460
|
ingestRecordCount = parseIngestRecordCount(run2.output);
|
|
4038
4461
|
if (ingestRecordCount != null) {
|
|
4039
4462
|
track("AI Wizard Ingest Successful", {
|
|
4040
|
-
entity_name:
|
|
4463
|
+
entity_name: confirmed3?.map((e) => e.name).join(", ") || "unknown",
|
|
4041
4464
|
record_count: ingestRecordCount,
|
|
4042
4465
|
duration_ms: ingestDurationMs
|
|
4043
4466
|
});
|
|
@@ -4050,7 +4473,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4050
4473
|
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
|
|
4051
4474
|
logger.warn(
|
|
4052
4475
|
{
|
|
4053
|
-
|
|
4476
|
+
language: ingestionProfile.id,
|
|
4054
4477
|
entrypoint: ingestEntrypoint,
|
|
4055
4478
|
reason: run2.reason
|
|
4056
4479
|
},
|
|
@@ -4073,7 +4496,7 @@ ${run2.output}` : status;
|
|
|
4073
4496
|
outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
|
|
4074
4497
|
logger.warn(
|
|
4075
4498
|
{
|
|
4076
|
-
|
|
4499
|
+
language: ingestionProfile.id,
|
|
4077
4500
|
entrypoint: ingestEntrypoint,
|
|
4078
4501
|
output: run2.output
|
|
4079
4502
|
},
|
|
@@ -4090,10 +4513,28 @@ ${run2.output}` : status;
|
|
|
4090
4513
|
}
|
|
4091
4514
|
}
|
|
4092
4515
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
4093
|
-
if (
|
|
4516
|
+
if (ingestEntrypoint) {
|
|
4094
4517
|
commandMessages.push(
|
|
4095
|
-
`Ingestion command: ${buildIngestCommand(worktree,
|
|
4518
|
+
`Ingestion command: ${buildIngestCommand(worktree, toolchain, ingestEntrypoint)}`
|
|
4096
4519
|
);
|
|
4520
|
+
if (toolchain.ingest.kind === "manual") {
|
|
4521
|
+
commandMessages.push(
|
|
4522
|
+
`The wizard does not run ${ingestionProfile.displayName} ${toolchain.packageManager.id} projects \u2014 run the command above yourself to ingest.`
|
|
4523
|
+
);
|
|
4524
|
+
const missingTask = await missingBuildTask(worktree, toolchain);
|
|
4525
|
+
if (missingTask) {
|
|
4526
|
+
const warning = `\u26A0\uFE0F The command above needs a "${missingTask}" task, which is not in ${toolchain.packageManager.dependency.mode === "agent-declares" ? toolchain.packageManager.dependency.file : "the build file"} \u2014 add it before running, or run the script through your IDE instead.`;
|
|
4527
|
+
commandMessages.push(warning);
|
|
4528
|
+
summaries.push(warning);
|
|
4529
|
+
}
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
if (ingestionSource === "local") {
|
|
4533
|
+
const limitation = localSourceLimitation(worktree, ingestionProfile);
|
|
4534
|
+
if (limitation) {
|
|
4535
|
+
commandMessages.push(`\u26A0\uFE0F ${limitation}`);
|
|
4536
|
+
summaries.push(`\u26A0\uFE0F ${limitation}`);
|
|
4537
|
+
}
|
|
4097
4538
|
}
|
|
4098
4539
|
await ctx.requestUserInput({
|
|
4099
4540
|
// No question being asked here, just an acknowledgement — the
|
|
@@ -4104,7 +4545,20 @@ ${run2.output}` : status;
|
|
|
4104
4545
|
messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
|
|
4105
4546
|
});
|
|
4106
4547
|
}
|
|
4107
|
-
|
|
4548
|
+
const skipSearch = useCases.includes("search") && !canScaffoldSearchUI(input.searchStrategy);
|
|
4549
|
+
if (skipSearch) {
|
|
4550
|
+
const target = describeSearchTarget(
|
|
4551
|
+
input.searchStrategy,
|
|
4552
|
+
input.frameworkName
|
|
4553
|
+
);
|
|
4554
|
+
summaries.push(
|
|
4555
|
+
`Search UI skipped: the wizard can't scaffold a native search UI for ${target}. Your records are in the "${targetIndex}" index \u2014 build the UI with Algolia's mobile InstantSearch libraries (https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/ios/ for iOS, .../android for Android).`
|
|
4556
|
+
);
|
|
4557
|
+
track("AI Wizard Search UI Skipped", {
|
|
4558
|
+
framework: input.frameworkName ?? "unknown"
|
|
4559
|
+
});
|
|
4560
|
+
}
|
|
4561
|
+
if (useCases.includes("search") && !skipSearch) {
|
|
4108
4562
|
let extraInstructions = [];
|
|
4109
4563
|
const preSearchFiles = new Set(await listChangedFiles(worktree));
|
|
4110
4564
|
for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
|
|
@@ -4175,9 +4629,9 @@ ${run2.output}` : status;
|
|
|
4175
4629
|
"implement: agent reported success but no files changed in the worktree"
|
|
4176
4630
|
);
|
|
4177
4631
|
}
|
|
4178
|
-
if (
|
|
4632
|
+
if (failedInstalls.size > 0) {
|
|
4179
4633
|
summaries.push(
|
|
4180
|
-
|
|
4634
|
+
`\u26A0\uFE0F Dependency install in the worktree failed. Install the ${[...failedInstalls].join(" and ")} dependencies in the worktree before the command below, or it will fail on a missing package.`
|
|
4181
4635
|
);
|
|
4182
4636
|
}
|
|
4183
4637
|
return {
|
|
@@ -4185,10 +4639,10 @@ ${run2.output}` : status;
|
|
|
4185
4639
|
filesChanged,
|
|
4186
4640
|
summary: summaries.join("\n\n"),
|
|
4187
4641
|
worktreePath: worktree,
|
|
4188
|
-
...useCases.includes("ingestion") &&
|
|
4642
|
+
...useCases.includes("ingestion") && ingestEntrypoint ? {
|
|
4189
4643
|
ingestCommand: buildIngestCommand(
|
|
4190
4644
|
worktree,
|
|
4191
|
-
|
|
4645
|
+
toolchain,
|
|
4192
4646
|
ingestEntrypoint
|
|
4193
4647
|
),
|
|
4194
4648
|
ingestScriptRan,
|
|
@@ -4517,20 +4971,20 @@ function parseCliArgs(argv) {
|
|
|
4517
4971
|
}
|
|
4518
4972
|
|
|
4519
4973
|
// src/lib/resetState.ts
|
|
4520
|
-
import { readdir as
|
|
4521
|
-
import { join as
|
|
4974
|
+
import { readdir as readdir5, rm as rm2 } from "node:fs/promises";
|
|
4975
|
+
import { join as join14 } from "node:path";
|
|
4522
4976
|
var KEEP = ["wizard.log"];
|
|
4523
4977
|
async function resetProjectState() {
|
|
4524
4978
|
const dir = stateDir();
|
|
4525
4979
|
let entries;
|
|
4526
4980
|
try {
|
|
4527
|
-
entries = await
|
|
4981
|
+
entries = await readdir5(dir);
|
|
4528
4982
|
} catch {
|
|
4529
4983
|
return { dir, removed: [] };
|
|
4530
4984
|
}
|
|
4531
4985
|
const targets = entries.filter((name) => !KEEP.includes(name));
|
|
4532
4986
|
await Promise.all(
|
|
4533
|
-
targets.map((name) => rm2(
|
|
4987
|
+
targets.map((name) => rm2(join14(dir, name), { recursive: true, force: true }))
|
|
4534
4988
|
);
|
|
4535
4989
|
return { dir, removed: targets };
|
|
4536
4990
|
}
|