@algolia/wizard 0.9.0-rc.80.70 → 0.9.0-rc.84.73
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 +347 -1202
- package/docs/algolia-sdk/README.md +26 -47
- package/docs/algolia-sdk/search-single-index.md +42 -0
- package/package.json +2 -2
- package/docs/algolia-sdk/instantsearch-setup-templates.md +0 -92
- package/docs/algolia-sdk/save-records-csharp.md +0 -71
- package/docs/algolia-sdk/save-records-go.md +0 -62
- package/docs/algolia-sdk/save-records-java.md +0 -66
- package/docs/algolia-sdk/save-records-kotlin.md +0 -60
- package/docs/algolia-sdk/save-records-php.md +0 -50
- package/docs/algolia-sdk/save-records-python.md +0 -51
- package/docs/algolia-sdk/save-records-ruby.md +0 -48
- package/docs/algolia-sdk/save-records-scala.md +0 -68
package/dist/main.js
CHANGED
|
@@ -287,11 +287,13 @@ var useWizard = create((set, get) => ({
|
|
|
287
287
|
// may repeat or duplicate on-screen content and isn't the useful signal
|
|
288
288
|
// here.
|
|
289
289
|
submitInput: async (value) => {
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
const resolve4 = get()._resolve;
|
|
291
|
+
if (!resolve4) return;
|
|
292
292
|
set({ inputReq: null, _resolve: null, phase: "running" });
|
|
293
293
|
const id = get().logStart("prompt", `User input: ${describeInputValue(value)}`);
|
|
294
294
|
get().logEnd(id, "success");
|
|
295
|
+
await markInteraction();
|
|
296
|
+
resolve4(value);
|
|
295
297
|
},
|
|
296
298
|
setDone: () => set({ phase: "done" }),
|
|
297
299
|
setError: (message) => set({ phase: "error", error: message }),
|
|
@@ -892,12 +894,12 @@ var sidebarItems = [
|
|
|
892
894
|
description: "push 100 records to Algolia in seconds"
|
|
893
895
|
},
|
|
894
896
|
{
|
|
895
|
-
title: "detect your
|
|
896
|
-
description: "React, Vue, Angular,
|
|
897
|
+
title: "detect your framework",
|
|
898
|
+
description: "React, Vue, Angular, Vanilla JS"
|
|
897
899
|
},
|
|
898
900
|
{
|
|
899
901
|
title: "scaffold a search UI",
|
|
900
|
-
description: "a styled InstantSearch
|
|
902
|
+
description: "a styled InstantSearch component, wired into your app"
|
|
901
903
|
},
|
|
902
904
|
{
|
|
903
905
|
title: "ship it",
|
|
@@ -1003,7 +1005,7 @@ var accessItems = [
|
|
|
1003
1005
|
{
|
|
1004
1006
|
tag: "READ",
|
|
1005
1007
|
title: "Project files",
|
|
1006
|
-
description: "reads
|
|
1008
|
+
description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
1007
1009
|
},
|
|
1008
1010
|
{
|
|
1009
1011
|
tag: "WRITE",
|
|
@@ -2221,651 +2223,15 @@ function writeCredentialsTool(ctx) {
|
|
|
2221
2223
|
// src/lib/tools/searchFiles.ts
|
|
2222
2224
|
import { tool as tool7 } from "ai";
|
|
2223
2225
|
import z10 from "zod";
|
|
2224
|
-
import { readdir as
|
|
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";
|
|
2226
|
+
import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
|
|
2235
2227
|
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 CSHARP_PROJECT = `${INGEST_DIR}/ingest/ingest.csproj`;
|
|
2270
|
-
var VISIBLE_INGEST_DIR = "algolia-wizard";
|
|
2271
|
-
var PY_VENV = `${INGEST_DIR}/.venv`;
|
|
2272
|
-
var PY_VENV_PYTHON = `${PY_VENV}/bin/python`;
|
|
2273
|
-
var PY_REQUIREMENTS = `${INGEST_DIR}/requirements.txt`;
|
|
2274
|
-
var LANGUAGE_PROFILES = {
|
|
2275
|
-
javascript: {
|
|
2276
|
-
id: "javascript",
|
|
2277
|
-
displayName: "JavaScript/TypeScript",
|
|
2278
|
-
aliases: [
|
|
2279
|
-
"javascript",
|
|
2280
|
-
"js",
|
|
2281
|
-
"typescript",
|
|
2282
|
-
"ts",
|
|
2283
|
-
"node",
|
|
2284
|
-
"nodejs",
|
|
2285
|
-
"node.js",
|
|
2286
|
-
"bun",
|
|
2287
|
-
"deno",
|
|
2288
|
-
"ecmascript",
|
|
2289
|
-
"jsx",
|
|
2290
|
-
"tsx"
|
|
2291
|
-
],
|
|
2292
|
-
manifests: ["package.json"],
|
|
2293
|
-
// The concrete npm-family manager is resolved by detectPackageManager (it
|
|
2294
|
-
// honours the package.json `packageManager` field, which lockfiles can't
|
|
2295
|
-
// express), so one spec covers all four and `resolveToolchain` rewrites the
|
|
2296
|
-
// binary below.
|
|
2297
|
-
packageManagers: [
|
|
2298
|
-
{
|
|
2299
|
-
id: "npm",
|
|
2300
|
-
dependency: { mode: "agent-declares", file: "package.json" },
|
|
2301
|
-
installSteps: [{ argv: ["npm", "install"] }],
|
|
2302
|
-
ingest: {
|
|
2303
|
-
kind: "auto",
|
|
2304
|
-
argv: ["node", ENTRYPOINT_TOKEN],
|
|
2305
|
-
entrypointExtensions: [".mjs", ".cjs", ".js"]
|
|
2306
|
-
}
|
|
2307
|
-
}
|
|
2308
|
-
],
|
|
2309
|
-
sdk: { packageName: "algoliasearch", versionPin: "^5", docKey: "js" },
|
|
2310
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.mjs`,
|
|
2311
|
-
// package.json scripts are repo-defined, so they're resolved at run time by
|
|
2312
|
-
// repoVerification rather than listed here.
|
|
2313
|
-
verification: [],
|
|
2314
|
-
envReadInstruction: "Read them from `process.env`.",
|
|
2315
|
-
skipDirs: ["node_modules", "dist", "build", "coverage", ".next", "out"]
|
|
2316
|
-
},
|
|
2317
|
-
python: {
|
|
2318
|
-
id: "python",
|
|
2319
|
-
displayName: "Python",
|
|
2320
|
-
aliases: ["python", "python3", "py", "cpython"],
|
|
2321
|
-
manifests: [
|
|
2322
|
-
"pyproject.toml",
|
|
2323
|
-
"requirements.txt",
|
|
2324
|
-
"setup.py",
|
|
2325
|
-
"setup.cfg",
|
|
2326
|
-
"Pipfile"
|
|
2327
|
-
],
|
|
2328
|
-
// Deliberately one path for every Python repo: a wizard-owned venv under
|
|
2329
|
-
// .algolia-wizard. Reusing the project's uv/poetry environment would mean
|
|
2330
|
-
// mutating the developer's real dependency manifest and lockfile, and the
|
|
2331
|
-
// declare-here/install-there split is the main way ingestion silently ends
|
|
2332
|
-
// up without the SDK installed. The tradeoff: the script can import the
|
|
2333
|
-
// Algolia client and anything it declares itself, but not the project's own
|
|
2334
|
-
// packages (see the optional root-requirements step below).
|
|
2335
|
-
packageManagers: [
|
|
2336
|
-
{
|
|
2337
|
-
id: "pip-venv",
|
|
2338
|
-
dependency: { mode: "agent-declares", file: PY_REQUIREMENTS },
|
|
2339
|
-
installSteps: [
|
|
2340
|
-
{ argv: ["python3", "-m", "venv", PY_VENV] },
|
|
2341
|
-
{
|
|
2342
|
-
argv: [PY_VENV_PYTHON, "-m", "pip", "install", "-r", PY_REQUIREMENTS]
|
|
2343
|
-
},
|
|
2344
|
-
// Best-effort access to the project's own dependencies (DB drivers,
|
|
2345
|
-
// ORMs) when the repo pins them the classic way.
|
|
2346
|
-
{
|
|
2347
|
-
argv: [
|
|
2348
|
-
PY_VENV_PYTHON,
|
|
2349
|
-
"-m",
|
|
2350
|
-
"pip",
|
|
2351
|
-
"install",
|
|
2352
|
-
"-r",
|
|
2353
|
-
"requirements.txt"
|
|
2354
|
-
],
|
|
2355
|
-
requiresFile: "requirements.txt",
|
|
2356
|
-
optional: true
|
|
2357
|
-
}
|
|
2358
|
-
],
|
|
2359
|
-
ingest: {
|
|
2360
|
-
kind: "auto",
|
|
2361
|
-
argv: [PY_VENV_PYTHON, ENTRYPOINT_TOKEN],
|
|
2362
|
-
entrypointExtensions: [".py"]
|
|
2363
|
-
}
|
|
2364
|
-
}
|
|
2365
|
-
],
|
|
2366
|
-
sdk: {
|
|
2367
|
-
packageName: "algoliasearch",
|
|
2368
|
-
versionPin: ">=4,<5",
|
|
2369
|
-
docKey: "python"
|
|
2370
|
-
},
|
|
2371
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.py`,
|
|
2372
|
-
localSourceCaveat: {
|
|
2373
|
-
unless: "requirements.txt",
|
|
2374
|
-
message: "The ingestion script runs in its own environment under .algolia-wizard/, so it can install the Algolia client but not this project's packages (no requirements.txt to install from). If the script needs your database driver or ORM, add those packages to .algolia-wizard/requirements.txt and re-run the install."
|
|
2375
|
-
},
|
|
2376
|
-
verification: [
|
|
2377
|
-
{
|
|
2378
|
-
// -x skips the venv this same directory holds; without it the check
|
|
2379
|
-
// compiles every installed package instead of the generated script.
|
|
2380
|
-
label: "python compileall",
|
|
2381
|
-
argv: ["python3", "-m", "compileall", "-q", "-x", "[.]venv", INGEST_DIR],
|
|
2382
|
-
requiresFile: INGEST_DIR
|
|
2383
|
-
}
|
|
2384
|
-
],
|
|
2385
|
-
envReadInstruction: "Read them from `os.environ`.",
|
|
2386
|
-
skipDirs: [
|
|
2387
|
-
"venv",
|
|
2388
|
-
"__pycache__",
|
|
2389
|
-
"site-packages",
|
|
2390
|
-
"dist",
|
|
2391
|
-
"build",
|
|
2392
|
-
"htmlcov"
|
|
2393
|
-
]
|
|
2394
|
-
},
|
|
2395
|
-
ruby: {
|
|
2396
|
-
id: "ruby",
|
|
2397
|
-
displayName: "Ruby",
|
|
2398
|
-
aliases: ["ruby", "rb", "rails", "ruby on rails", "rubyonrails"],
|
|
2399
|
-
manifests: ["Gemfile", "*.gemspec"],
|
|
2400
|
-
packageManagers: [
|
|
2401
|
-
{
|
|
2402
|
-
id: "bundler",
|
|
2403
|
-
dependency: { mode: "agent-declares", file: "Gemfile" },
|
|
2404
|
-
installSteps: [{ argv: ["bundle", "install"] }],
|
|
2405
|
-
ingest: {
|
|
2406
|
-
kind: "auto",
|
|
2407
|
-
argv: ["bundle", "exec", "ruby", ENTRYPOINT_TOKEN],
|
|
2408
|
-
entrypointExtensions: [".rb"]
|
|
2409
|
-
}
|
|
2410
|
-
}
|
|
2411
|
-
],
|
|
2412
|
-
sdk: { packageName: "algolia", versionPin: "~> 3.0", docKey: "ruby" },
|
|
2413
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.rb`,
|
|
2414
|
-
// Ruby has no directory-level syntax check (`ruby -c` is one file at a
|
|
2415
|
-
// time), so verification relies on the agent's own review here.
|
|
2416
|
-
verification: [],
|
|
2417
|
-
envReadInstruction: "Read them from `ENV.fetch('NAME')`.",
|
|
2418
|
-
skipDirs: ["vendor", "tmp", "log", "coverage"]
|
|
2419
|
-
},
|
|
2420
|
-
php: {
|
|
2421
|
-
id: "php",
|
|
2422
|
-
displayName: "PHP",
|
|
2423
|
-
aliases: ["php", "laravel", "symfony"],
|
|
2424
|
-
manifests: ["composer.json"],
|
|
2425
|
-
packageManagers: [
|
|
2426
|
-
{
|
|
2427
|
-
id: "composer",
|
|
2428
|
-
// `composer require` both declares and installs, and unlike editing
|
|
2429
|
-
// composer.json by hand it can't leave composer.lock out of date (which
|
|
2430
|
-
// makes a later `composer install` refuse to run).
|
|
2431
|
-
dependency: { mode: "wizard-installs" },
|
|
2432
|
-
installSteps: [
|
|
2433
|
-
{
|
|
2434
|
-
argv: [
|
|
2435
|
-
"composer",
|
|
2436
|
-
"require",
|
|
2437
|
-
"algolia/algoliasearch-client-php:^4",
|
|
2438
|
-
"--no-interaction",
|
|
2439
|
-
// Repo post-install scripts are the project's code, not ours to
|
|
2440
|
-
// trigger; Laravel's package:discover also fails in a bare tree.
|
|
2441
|
-
"--no-scripts"
|
|
2442
|
-
]
|
|
2443
|
-
}
|
|
2444
|
-
],
|
|
2445
|
-
ingest: {
|
|
2446
|
-
kind: "auto",
|
|
2447
|
-
argv: ["php", ENTRYPOINT_TOKEN],
|
|
2448
|
-
entrypointExtensions: [".php"]
|
|
2449
|
-
}
|
|
2450
|
-
}
|
|
2451
|
-
],
|
|
2452
|
-
sdk: {
|
|
2453
|
-
packageName: "algolia/algoliasearch-client-php",
|
|
2454
|
-
versionPin: "^4",
|
|
2455
|
-
docKey: "php"
|
|
2456
|
-
},
|
|
2457
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.php`,
|
|
2458
|
-
verification: [],
|
|
2459
|
-
envReadInstruction: "Read them from `getenv('NAME')`.",
|
|
2460
|
-
skipDirs: ["vendor", "node_modules"]
|
|
2461
|
-
},
|
|
2462
|
-
go: {
|
|
2463
|
-
id: "go",
|
|
2464
|
-
displayName: "Go",
|
|
2465
|
-
aliases: ["go", "golang"],
|
|
2466
|
-
manifests: ["go.mod"],
|
|
2467
|
-
packageManagers: [
|
|
2468
|
-
{
|
|
2469
|
-
id: "gomod",
|
|
2470
|
-
// Imports in the generated file are the declaration; `go mod tidy`
|
|
2471
|
-
// resolves and fetches them — which only works because the script lives
|
|
2472
|
-
// outside INGEST_DIR (see VISIBLE_INGEST_DIR).
|
|
2473
|
-
dependency: { mode: "code-imports" },
|
|
2474
|
-
installSteps: [{ argv: ["go", "mod", "tidy"] }],
|
|
2475
|
-
ingest: {
|
|
2476
|
-
kind: "auto",
|
|
2477
|
-
argv: ["go", "run", ENTRYPOINT_TOKEN],
|
|
2478
|
-
entrypointExtensions: [".go"]
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
],
|
|
2482
|
-
sdk: {
|
|
2483
|
-
packageName: "github.com/algolia/algoliasearch-client-go/v4",
|
|
2484
|
-
versionPin: "v4",
|
|
2485
|
-
docKey: "go"
|
|
2486
|
-
},
|
|
2487
|
-
ingestEntrypointExample: `${VISIBLE_INGEST_DIR}/ingest.go`,
|
|
2488
|
-
verification: [
|
|
2489
|
-
{ label: "go vet", argv: ["go", "vet", "./..."], requiresFile: "go.mod" }
|
|
2490
|
-
],
|
|
2491
|
-
envReadInstruction: "Read them from `os.Getenv`.",
|
|
2492
|
-
skipDirs: ["vendor", "bin"]
|
|
2493
|
-
},
|
|
2494
|
-
java: {
|
|
2495
|
-
id: "java",
|
|
2496
|
-
displayName: "Java",
|
|
2497
|
-
aliases: ["java"],
|
|
2498
|
-
manifests: ["pom.xml", "build.gradle", "build.gradle.kts"],
|
|
2499
|
-
packageManagers: [
|
|
2500
|
-
{
|
|
2501
|
-
id: "maven",
|
|
2502
|
-
detectFiles: ["pom.xml"],
|
|
2503
|
-
sdkVersionPin: "[4,5)",
|
|
2504
|
-
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2505
|
-
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2506
|
-
// The main class is a wizard constant the instructions require the agent
|
|
2507
|
-
// to use, so execution can't be redirected by agent output. Runnable only
|
|
2508
|
-
// because the install step above compiles src/main/java first — which is
|
|
2509
|
-
// why the entrypoint lives there rather than under .algolia-wizard/.
|
|
2510
|
-
ingest: {
|
|
2511
|
-
kind: "auto",
|
|
2512
|
-
argv: [
|
|
2513
|
-
"mvn",
|
|
2514
|
-
"-q",
|
|
2515
|
-
"org.codehaus.mojo:exec-maven-plugin:3.5.0:java",
|
|
2516
|
-
"-Dexec.mainClass=AlgoliaWizardIngest"
|
|
2517
|
-
],
|
|
2518
|
-
entrypointExtensions: [".java"]
|
|
2519
|
-
}
|
|
2520
|
-
},
|
|
2521
|
-
{
|
|
2522
|
-
id: "gradle",
|
|
2523
|
-
detectFiles: ["build.gradle", "build.gradle.kts"],
|
|
2524
|
-
dependency: {
|
|
2525
|
-
mode: "agent-declares",
|
|
2526
|
-
file: "build.gradle",
|
|
2527
|
-
alternatives: ["build.gradle.kts"]
|
|
2528
|
-
},
|
|
2529
|
-
installSteps: [],
|
|
2530
|
-
// Auto-running means executing the repo's own ./gradlew wrapper; out of
|
|
2531
|
-
// scope for now, so the wizard writes the code and prints the command.
|
|
2532
|
-
ingest: {
|
|
2533
|
-
kind: "manual",
|
|
2534
|
-
entrypointExtensions: [".java"],
|
|
2535
|
-
runCommand: "./gradlew runAlgoliaIngest",
|
|
2536
|
-
requiresBuildTask: "runAlgoliaIngest"
|
|
2537
|
-
}
|
|
2538
|
-
}
|
|
2539
|
-
],
|
|
2540
|
-
sdk: {
|
|
2541
|
-
packageName: "com.algolia:algoliasearch",
|
|
2542
|
-
versionPin: "4.+",
|
|
2543
|
-
docKey: "java",
|
|
2544
|
-
alsoRequires: "The class must be named AlgoliaWizardIngest, in the default package (no `package` statement), with a `public static void main`."
|
|
2545
|
-
},
|
|
2546
|
-
// Not under .algolia-wizard/: Maven and Gradle only compile src/main/<lang>,
|
|
2547
|
-
// so a class outside it never makes it onto the classpath and the run command
|
|
2548
|
-
// fails with "class not found".
|
|
2549
|
-
ingestEntrypointExample: "src/main/java/AlgoliaWizardIngest.java",
|
|
2550
|
-
verification: [
|
|
2551
|
-
{
|
|
2552
|
-
label: "mvn compile",
|
|
2553
|
-
argv: ["mvn", "-q", "-DskipTests", "compile"],
|
|
2554
|
-
requiresFile: "pom.xml"
|
|
2555
|
-
}
|
|
2556
|
-
],
|
|
2557
|
-
envReadInstruction: "Read them from `System.getenv`.",
|
|
2558
|
-
skipDirs: ["target", "build", "out"]
|
|
2559
|
-
},
|
|
2560
|
-
kotlin: {
|
|
2561
|
-
id: "kotlin",
|
|
2562
|
-
displayName: "Kotlin",
|
|
2563
|
-
aliases: ["kotlin", "kt", "ktor"],
|
|
2564
|
-
manifests: ["build.gradle.kts", "build.gradle", "pom.xml"],
|
|
2565
|
-
packageManagers: [
|
|
2566
|
-
{
|
|
2567
|
-
id: "gradle",
|
|
2568
|
-
detectFiles: ["build.gradle.kts", "build.gradle"],
|
|
2569
|
-
dependency: {
|
|
2570
|
-
mode: "agent-declares",
|
|
2571
|
-
file: "build.gradle.kts",
|
|
2572
|
-
alternatives: ["build.gradle"]
|
|
2573
|
-
},
|
|
2574
|
-
installSteps: [],
|
|
2575
|
-
ingest: {
|
|
2576
|
-
kind: "manual",
|
|
2577
|
-
entrypointExtensions: [".kt"],
|
|
2578
|
-
runCommand: "./gradlew runAlgoliaIngest",
|
|
2579
|
-
requiresBuildTask: "runAlgoliaIngest"
|
|
2580
|
-
}
|
|
2581
|
-
},
|
|
2582
|
-
// Kotlin/Maven is rare but real, and pom.xml is a Kotlin manifest — without
|
|
2583
|
-
// this spec such a repo falls through to Gradle and is told to run a
|
|
2584
|
-
// ./gradlew task that doesn't exist. Compiling needs the repo's own
|
|
2585
|
-
// kotlin-maven-plugin, so the run stays the developer's step.
|
|
2586
|
-
{
|
|
2587
|
-
id: "maven",
|
|
2588
|
-
detectFiles: ["pom.xml"],
|
|
2589
|
-
sdkVersionPin: "[3,4)",
|
|
2590
|
-
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2591
|
-
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2592
|
-
ingest: {
|
|
2593
|
-
kind: "manual",
|
|
2594
|
-
entrypointExtensions: [".kt"],
|
|
2595
|
-
runCommand: "mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java -Dexec.mainClass=AlgoliaWizardIngest"
|
|
2596
|
-
}
|
|
2597
|
-
}
|
|
2598
|
-
],
|
|
2599
|
-
sdk: {
|
|
2600
|
-
packageName: "com.algolia:algoliasearch-client-kotlin",
|
|
2601
|
-
versionPin: "3.+",
|
|
2602
|
-
docKey: "kotlin",
|
|
2603
|
-
// The published client's commonMain ships only ktor-client-core; without an
|
|
2604
|
-
// engine the script compiles and then fails at its first request.
|
|
2605
|
-
alsoRequires: "The Kotlin client bundles no HTTP engine, so also declare one (e.g. io.ktor:ktor-client-okhttp). Name the object AlgoliaWizardIngest in the default package, with a @JvmStatic main."
|
|
2606
|
-
},
|
|
2607
|
-
ingestEntrypointExample: "src/main/kotlin/AlgoliaWizardIngest.kt",
|
|
2608
|
-
verification: [],
|
|
2609
|
-
envReadInstruction: "Read them from `System.getenv`.",
|
|
2610
|
-
skipDirs: ["build", "out"]
|
|
2611
|
-
},
|
|
2612
|
-
scala: {
|
|
2613
|
-
id: "scala",
|
|
2614
|
-
displayName: "Scala",
|
|
2615
|
-
aliases: ["scala", "sbt"],
|
|
2616
|
-
manifests: ["build.sbt", "build.sc"],
|
|
2617
|
-
packageManagers: [
|
|
2618
|
-
{
|
|
2619
|
-
id: "sbt",
|
|
2620
|
-
dependency: { mode: "agent-declares", file: "build.sbt" },
|
|
2621
|
-
installSteps: [],
|
|
2622
|
-
ingest: {
|
|
2623
|
-
kind: "manual",
|
|
2624
|
-
entrypointExtensions: [".scala"],
|
|
2625
|
-
runCommand: 'sbt "runMain AlgoliaWizardIngest"'
|
|
2626
|
-
}
|
|
2627
|
-
}
|
|
2628
|
-
],
|
|
2629
|
-
sdk: {
|
|
2630
|
-
packageName: "com.algolia:algoliasearch-scala_2.13",
|
|
2631
|
-
versionPin: "2.+",
|
|
2632
|
-
docKey: "scala",
|
|
2633
|
-
alsoRequires: "Name the object AlgoliaWizardIngest in the default package (no `package` statement) so `runMain AlgoliaWizardIngest` resolves it."
|
|
2634
|
-
},
|
|
2635
|
-
ingestEntrypointExample: "src/main/scala/AlgoliaWizardIngest.scala",
|
|
2636
|
-
verification: [],
|
|
2637
|
-
envReadInstruction: "Read them from `sys.env`.",
|
|
2638
|
-
// `project/` holds sbt's build definition, but the name is generic enough
|
|
2639
|
-
// that some repos use it for source; scanning it is cheap, missing source
|
|
2640
|
-
// is not.
|
|
2641
|
-
skipDirs: ["target"]
|
|
2642
|
-
},
|
|
2643
|
-
csharp: {
|
|
2644
|
-
id: "csharp",
|
|
2645
|
-
displayName: "C#",
|
|
2646
|
-
aliases: ["c#", "csharp", "cs", ".net", "dotnet", "net", "asp.net"],
|
|
2647
|
-
manifests: ["*.csproj", "*.sln", "global.json"],
|
|
2648
|
-
packageManagers: [
|
|
2649
|
-
{
|
|
2650
|
-
id: "dotnet",
|
|
2651
|
-
// A self-contained project under .algolia-wizard keeps the ingest script
|
|
2652
|
-
// out of the repo's own build graph.
|
|
2653
|
-
dependency: { mode: "agent-declares", file: CSHARP_PROJECT },
|
|
2654
|
-
installSteps: [{ argv: ["dotnet", "restore", CSHARP_PROJECT] }],
|
|
2655
|
-
ingest: {
|
|
2656
|
-
kind: "auto",
|
|
2657
|
-
argv: ["dotnet", "run", "--project", ENTRYPOINT_TOKEN],
|
|
2658
|
-
entrypointExtensions: [".csproj"]
|
|
2659
|
-
}
|
|
2660
|
-
}
|
|
2661
|
-
],
|
|
2662
|
-
sdk: {
|
|
2663
|
-
packageName: "Algolia.Search",
|
|
2664
|
-
versionPin: "7.*",
|
|
2665
|
-
docKey: "csharp"
|
|
2666
|
-
},
|
|
2667
|
-
ingestEntrypointExample: CSHARP_PROJECT,
|
|
2668
|
-
verification: [
|
|
2669
|
-
{
|
|
2670
|
-
label: "dotnet build",
|
|
2671
|
-
argv: ["dotnet", "build", CSHARP_PROJECT, "--nologo"],
|
|
2672
|
-
requiresFile: CSHARP_PROJECT
|
|
2673
|
-
}
|
|
2674
|
-
],
|
|
2675
|
-
envReadInstruction: 'Read them from `Environment.GetEnvironmentVariable("NAME")`.',
|
|
2676
|
-
// Deliberately not `packages`: modern .NET uses PackageReference, and
|
|
2677
|
-
// `packages/` is where pnpm/Lerna/Turborepo monorepos keep all their source —
|
|
2678
|
-
// skipping it would hide the entities the scan is looking for.
|
|
2679
|
-
skipDirs: ["bin", "obj"]
|
|
2680
|
-
}
|
|
2681
|
-
};
|
|
2682
|
-
var DEFAULT_LANGUAGE_ID = "javascript";
|
|
2683
|
-
var JAVASCRIPT = "javascript";
|
|
2684
|
-
var CURATED_LANGUAGES = Object.values(
|
|
2685
|
-
LANGUAGE_PROFILES
|
|
2686
|
-
).map((profile) => profile.displayName);
|
|
2687
|
-
function isBackendLanguage(profile) {
|
|
2688
|
-
return profile.id !== JAVASCRIPT;
|
|
2689
|
-
}
|
|
2690
|
-
function normalizeLanguageName(name) {
|
|
2691
|
-
return name.toLowerCase().trim().replace(/[\s_-]+/g, "");
|
|
2692
|
-
}
|
|
2693
|
-
var ALIAS_TO_ID = /* @__PURE__ */ new Map();
|
|
2694
|
-
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
2695
|
-
for (const alias of [profile.id, profile.displayName, ...profile.aliases]) {
|
|
2696
|
-
ALIAS_TO_ID.set(normalizeLanguageName(alias), profile.id);
|
|
2697
|
-
}
|
|
2698
|
-
}
|
|
2699
|
-
function resolveLanguageProfile(name) {
|
|
2700
|
-
const id = ALIAS_TO_ID.get(normalizeLanguageName(name));
|
|
2701
|
-
return id ? LANGUAGE_PROFILES[id] : void 0;
|
|
2702
|
-
}
|
|
2703
|
-
function isSameLanguage(a, b) {
|
|
2704
|
-
const x = resolveLanguageProfile(a);
|
|
2705
|
-
const y = resolveLanguageProfile(b);
|
|
2706
|
-
if (x && y) return x.id === y.id;
|
|
2707
|
-
if (x || y) return false;
|
|
2708
|
-
const folded = normalizeLanguageName(a);
|
|
2709
|
-
return folded !== "" && folded === normalizeLanguageName(b);
|
|
2710
|
-
}
|
|
2711
|
-
var BASE_SKIP_DIRS = ["node_modules", ".git", "dist"];
|
|
2712
|
-
var ALL_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2713
|
-
...BASE_SKIP_DIRS,
|
|
2714
|
-
...Object.values(LANGUAGE_PROFILES).flatMap((p) => p.skipDirs)
|
|
2715
|
-
]);
|
|
2716
|
-
var ALLOWED_BINARIES = new Set(
|
|
2717
|
-
Object.values(LANGUAGE_PROFILES).flatMap((profile) => [
|
|
2718
|
-
...profile.packageManagers.flatMap((pm) => [
|
|
2719
|
-
...pm.installSteps.map((s) => s.argv[0]),
|
|
2720
|
-
...pm.ingest.kind === "auto" ? [pm.ingest.argv[0]] : []
|
|
2721
|
-
]),
|
|
2722
|
-
...profile.verification.map((v) => v.argv[0])
|
|
2723
|
-
])
|
|
2724
|
-
);
|
|
2725
|
-
var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
|
|
2726
|
-
function isWorktreeRelativeCommand(command) {
|
|
2727
|
-
return command.includes("/");
|
|
2728
|
-
}
|
|
2729
|
-
function withCommand(argv, command) {
|
|
2730
|
-
return [command, ...argv.slice(1)];
|
|
2731
|
-
}
|
|
2732
|
-
function resolveDeclaredManifest(root, packageManager) {
|
|
2733
|
-
const { dependency } = packageManager;
|
|
2734
|
-
if (dependency.mode !== "agent-declares" || !dependency.alternatives?.length) {
|
|
2735
|
-
return packageManager;
|
|
2736
|
-
}
|
|
2737
|
-
const present = [dependency.file, ...dependency.alternatives].find(
|
|
2738
|
-
(file) => existsSync2(join9(root, file))
|
|
2739
|
-
);
|
|
2740
|
-
if (!present || present === dependency.file) return packageManager;
|
|
2741
|
-
return { ...packageManager, dependency: { ...dependency, file: present } };
|
|
2742
|
-
}
|
|
2743
|
-
async function manifestPresent(root, manifest, listing) {
|
|
2744
|
-
if (!manifest.startsWith("*.")) return existsSync2(join9(root, manifest));
|
|
2745
|
-
if (!listing.entries) {
|
|
2746
|
-
const entries = await readdir2(root).catch(() => []);
|
|
2747
|
-
listing.entries = Array.isArray(entries) ? entries : [];
|
|
2748
|
-
}
|
|
2749
|
-
const suffix = manifest.slice(1);
|
|
2750
|
-
return listing.entries.some((e) => e.endsWith(suffix));
|
|
2751
|
-
}
|
|
2752
|
-
async function profileManifestPresent(root, profile, listing) {
|
|
2753
|
-
for (const manifest of profile.manifests) {
|
|
2754
|
-
if (await manifestPresent(root, manifest, listing)) return true;
|
|
2755
|
-
}
|
|
2756
|
-
return false;
|
|
2757
|
-
}
|
|
2758
|
-
async function detectProfilesFromManifests(root) {
|
|
2759
|
-
const listing = {};
|
|
2760
|
-
const found = [];
|
|
2761
|
-
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
2762
|
-
if (await profileManifestPresent(root, profile, listing)) found.push(profile);
|
|
2763
|
-
}
|
|
2764
|
-
return found;
|
|
2765
|
-
}
|
|
2766
|
-
async function hasProfileManifest(root, profile) {
|
|
2767
|
-
return profileManifestPresent(root, profile, {});
|
|
2768
|
-
}
|
|
2769
|
-
async function pickIngestionCandidates(root, confirmedNames) {
|
|
2770
|
-
const confirmed3 = confirmedNames.map((name) => resolveLanguageProfile(name)).filter((p) => p !== void 0);
|
|
2771
|
-
const onDisk = await detectProfilesFromManifests(root);
|
|
2772
|
-
const onDiskIds = new Set(onDisk.map((p) => p.id));
|
|
2773
|
-
const candidates = [
|
|
2774
|
-
...new Map(
|
|
2775
|
-
confirmed3.filter((p) => onDiskIds.has(p.id)).map((p) => [p.id, p])
|
|
2776
|
-
).values()
|
|
2777
|
-
];
|
|
2778
|
-
return { candidates, confirmed: confirmed3, onDisk };
|
|
2779
|
-
}
|
|
2780
|
-
async function resolveToolchain(root, profile) {
|
|
2781
|
-
const signals = (pm) => [
|
|
2782
|
-
...pm.lockfiles ?? [],
|
|
2783
|
-
...pm.detectFiles ?? []
|
|
2784
|
-
];
|
|
2785
|
-
const matched = profile.packageManagers.find(
|
|
2786
|
-
(pm) => signals(pm).some((f) => existsSync2(join9(root, f)))
|
|
2787
|
-
);
|
|
2788
|
-
const fallback = profile.packageManagers.find((pm) => signals(pm).length === 0) ?? profile.packageManagers[0];
|
|
2789
|
-
const packageManager = resolveDeclaredManifest(root, matched ?? fallback);
|
|
2790
|
-
let { installSteps, ingest } = packageManager;
|
|
2791
|
-
installSteps = installSteps.map(
|
|
2792
|
-
(step) => isWorktreeRelativeCommand(step.argv[0]) ? { ...step, argv: withCommand(step.argv, join9(root, step.argv[0])) } : step
|
|
2793
|
-
);
|
|
2794
|
-
if (ingest.kind === "auto" && isWorktreeRelativeCommand(ingest.argv[0])) {
|
|
2795
|
-
ingest = {
|
|
2796
|
-
...ingest,
|
|
2797
|
-
argv: withCommand(ingest.argv, join9(root, ingest.argv[0]))
|
|
2798
|
-
};
|
|
2799
|
-
}
|
|
2800
|
-
if (profile.id === "javascript") {
|
|
2801
|
-
const pm = await detectPackageManager(root);
|
|
2802
|
-
if (JS_PACKAGE_MANAGERS.has(pm)) {
|
|
2803
|
-
installSteps = installSteps.map((step) => ({
|
|
2804
|
-
...step,
|
|
2805
|
-
argv: withCommand(step.argv, pm)
|
|
2806
|
-
}));
|
|
2807
|
-
if (pm === "bun" && ingest.kind === "auto") {
|
|
2808
|
-
ingest = { ...ingest, argv: withCommand(ingest.argv, "bun") };
|
|
2809
|
-
}
|
|
2810
|
-
}
|
|
2811
|
-
}
|
|
2812
|
-
return { profile, packageManager, installSteps, ingest };
|
|
2813
|
-
}
|
|
2814
|
-
function resolveIngestArgv(ingest, entrypoint) {
|
|
2815
|
-
if (ingest.kind !== "auto") {
|
|
2816
|
-
throw new Error("resolveIngestArgv called for a manual-run toolchain");
|
|
2817
|
-
}
|
|
2818
|
-
return ingest.argv.map(
|
|
2819
|
-
(part) => part === ENTRYPOINT_TOKEN ? entrypoint : part
|
|
2820
|
-
);
|
|
2821
|
-
}
|
|
2822
|
-
function describeIngestCommand(ingest, entrypoint) {
|
|
2823
|
-
if (ingest.kind !== "auto") return ingest.runCommand;
|
|
2824
|
-
return ingest.argv.map((part) => part === ENTRYPOINT_TOKEN ? shellQuote(entrypoint) : part).join(" ");
|
|
2825
|
-
}
|
|
2826
|
-
function ingestScriptDir(profile) {
|
|
2827
|
-
const parts = profile.ingestEntrypointExample.split("/");
|
|
2828
|
-
return parts.slice(0, -1).join("/") || ".";
|
|
2829
|
-
}
|
|
2830
|
-
function localSourceLimitation(root, profile) {
|
|
2831
|
-
const caveat = profile.localSourceCaveat;
|
|
2832
|
-
if (!caveat) return void 0;
|
|
2833
|
-
return existsSync2(join9(root, caveat.unless)) ? void 0 : caveat.message;
|
|
2834
|
-
}
|
|
2835
|
-
async function missingBuildTask(root, toolchain) {
|
|
2836
|
-
const { ingest, packageManager } = toolchain;
|
|
2837
|
-
if (ingest.kind !== "manual" || !ingest.requiresBuildTask) return void 0;
|
|
2838
|
-
if (packageManager.dependency.mode !== "agent-declares") return void 0;
|
|
2839
|
-
const buildFile = join9(root, packageManager.dependency.file);
|
|
2840
|
-
const contents = await readFile7(buildFile, "utf8").catch(() => void 0);
|
|
2841
|
-
if (contents === void 0) return void 0;
|
|
2842
|
-
return contents.includes(ingest.requiresBuildTask) ? void 0 : ingest.requiresBuildTask;
|
|
2843
|
-
}
|
|
2844
|
-
function sdkVersionPin(profile, packageManager) {
|
|
2845
|
-
return packageManager.sdkVersionPin ?? profile.sdk.versionPin;
|
|
2846
|
-
}
|
|
2847
|
-
function dependencyInstruction(toolchain) {
|
|
2848
|
-
const { profile, packageManager } = toolchain;
|
|
2849
|
-
const { packageName } = profile.sdk;
|
|
2850
|
-
const versionPin = sdkVersionPin(profile, packageManager);
|
|
2851
|
-
const also = profile.sdk.alsoRequires ? ` ${profile.sdk.alsoRequires}` : "";
|
|
2852
|
-
switch (packageManager.dependency.mode) {
|
|
2853
|
-
case "wizard-installs":
|
|
2854
|
-
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}`;
|
|
2855
|
-
case "code-imports":
|
|
2856
|
-
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}`;
|
|
2857
|
-
case "agent-declares":
|
|
2858
|
-
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}`;
|
|
2859
|
-
}
|
|
2860
|
-
}
|
|
2861
|
-
|
|
2862
|
-
// src/lib/tools/searchFiles.ts
|
|
2863
2228
|
var MAX_QUERY_LENGTH = 1e3;
|
|
2864
2229
|
async function walkFiles(dir) {
|
|
2230
|
+
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
2865
2231
|
const out = [];
|
|
2866
|
-
for (const e of await
|
|
2867
|
-
if (e.name.startsWith(".") ||
|
|
2868
|
-
const full =
|
|
2232
|
+
for (const e of await readdir2(dir, { withFileTypes: true })) {
|
|
2233
|
+
if (e.name.startsWith(".") || skip.has(e.name)) continue;
|
|
2234
|
+
const full = join8(dir, e.name);
|
|
2869
2235
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2870
2236
|
else if (e.isFile()) out.push(full);
|
|
2871
2237
|
}
|
|
@@ -2898,7 +2264,7 @@ function searchFilesTool(ctx) {
|
|
|
2898
2264
|
for (const file of await walkFiles(resolved.target)) {
|
|
2899
2265
|
let content;
|
|
2900
2266
|
try {
|
|
2901
|
-
content = await
|
|
2267
|
+
content = await readFile6(file, "utf8");
|
|
2902
2268
|
} catch {
|
|
2903
2269
|
continue;
|
|
2904
2270
|
}
|
|
@@ -2922,144 +2288,88 @@ function searchFilesTool(ctx) {
|
|
|
2922
2288
|
import { tool as tool8 } from "ai";
|
|
2923
2289
|
import z11 from "zod";
|
|
2924
2290
|
|
|
2925
|
-
// src/lib/tools/repoVerification.ts
|
|
2926
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
2927
|
-
import { join as join11 } from "node:path";
|
|
2928
|
-
|
|
2929
2291
|
// src/lib/tools/utils/runCommand.ts
|
|
2930
2292
|
import { spawn as spawn2 } from "node:child_process";
|
|
2931
|
-
|
|
2932
|
-
var INGEST_TIMEOUT_MS = 15 * 6e4;
|
|
2933
|
-
var VERIFY_TIMEOUT_MS = 10 * 6e4;
|
|
2934
|
-
var KILL_GRACE_MS = 5e3;
|
|
2935
|
-
function runCommand(command, args, options = {}) {
|
|
2936
|
-
const { cwd, env, timeoutMs = VERIFY_TIMEOUT_MS } = options;
|
|
2293
|
+
function runCommand(command, args, cwd) {
|
|
2937
2294
|
return new Promise((resolve4) => {
|
|
2938
2295
|
let output = "";
|
|
2939
|
-
let settled = false;
|
|
2940
2296
|
const child = spawn2(command, args, {
|
|
2941
2297
|
cwd,
|
|
2942
|
-
|
|
2943
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2944
|
-
...env ? { env: { ...process.env, ...env } } : {}
|
|
2298
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2945
2299
|
});
|
|
2946
|
-
const settle = (result) => {
|
|
2947
|
-
if (settled) return;
|
|
2948
|
-
settled = true;
|
|
2949
|
-
clearTimeout(timer);
|
|
2950
|
-
resolve4(result);
|
|
2951
|
-
};
|
|
2952
|
-
const timer = setTimeout(() => {
|
|
2953
|
-
child.kill("SIGTERM");
|
|
2954
|
-
setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
|
|
2955
|
-
const seconds = Math.round(timeoutMs / 1e3);
|
|
2956
|
-
settle({
|
|
2957
|
-
code: 1,
|
|
2958
|
-
output: `${output}
|
|
2959
|
-
Timed out after ${seconds}s: ${command} ${args.join(" ")}`.trim(),
|
|
2960
|
-
timedOut: true
|
|
2961
|
-
});
|
|
2962
|
-
}, timeoutMs);
|
|
2963
2300
|
child.stdout?.on("data", (d) => output += d);
|
|
2964
2301
|
child.stderr?.on("data", (d) => output += d);
|
|
2965
2302
|
child.on(
|
|
2966
2303
|
"error",
|
|
2967
|
-
(err) =>
|
|
2968
|
-
code: 1,
|
|
2969
|
-
output: `Failed to run ${command}: ${err.message}`,
|
|
2970
|
-
timedOut: false
|
|
2971
|
-
})
|
|
2972
|
-
);
|
|
2973
|
-
child.on(
|
|
2974
|
-
"close",
|
|
2975
|
-
(code) => settle({ code: code ?? 1, output, timedOut: false })
|
|
2304
|
+
(err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
|
|
2976
2305
|
);
|
|
2306
|
+
child.on("close", (code) => resolve4({ code: code ?? 1, output }));
|
|
2977
2307
|
});
|
|
2978
2308
|
}
|
|
2979
2309
|
|
|
2310
|
+
// src/lib/tools/utils/packageManager.ts
|
|
2311
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
2312
|
+
import { existsSync } from "node:fs";
|
|
2313
|
+
import { join as join9 } from "node:path";
|
|
2314
|
+
var LOCKFILES = [
|
|
2315
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
2316
|
+
["yarn.lock", "yarn"],
|
|
2317
|
+
["bun.lockb", "bun"],
|
|
2318
|
+
["bun.lock", "bun"],
|
|
2319
|
+
["package-lock.json", "npm"]
|
|
2320
|
+
];
|
|
2321
|
+
async function readPackageJson(cwd = process.cwd()) {
|
|
2322
|
+
return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
|
|
2323
|
+
}
|
|
2324
|
+
function packageManagerFrom(pkg) {
|
|
2325
|
+
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2326
|
+
}
|
|
2327
|
+
function packageManagerFromLockfile(cwd) {
|
|
2328
|
+
return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
|
|
2329
|
+
}
|
|
2330
|
+
async function detectPackageManager(cwd) {
|
|
2331
|
+
try {
|
|
2332
|
+
const pkg = await readPackageJson(cwd);
|
|
2333
|
+
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2334
|
+
} catch {
|
|
2335
|
+
}
|
|
2336
|
+
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2980
2339
|
// src/lib/tools/repoVerification.ts
|
|
2981
2340
|
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
2982
|
-
async function
|
|
2983
|
-
const { code, output } = await runCommand(binary, args, {
|
|
2984
|
-
timeoutMs: VERIFY_TIMEOUT_MS
|
|
2985
|
-
});
|
|
2986
|
-
return { command, exitCode: code, ok: code === 0, output: output.trim() };
|
|
2987
|
-
}
|
|
2988
|
-
async function javascriptChecks() {
|
|
2341
|
+
async function runRepoVerificationCheck() {
|
|
2989
2342
|
let pkg;
|
|
2990
2343
|
try {
|
|
2991
2344
|
pkg = await readPackageJson();
|
|
2992
2345
|
} catch (err) {
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
};
|
|
2346
|
+
const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
|
|
2347
|
+
return { ok: false, checks: [], limitation };
|
|
2996
2348
|
}
|
|
2997
2349
|
const scripts = pkg.scripts ?? {};
|
|
2998
2350
|
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
2999
2351
|
if (present.length === 0) {
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
};
|
|
2352
|
+
const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
|
|
2353
|
+
return { ok: false, checks: [], limitation };
|
|
3003
2354
|
}
|
|
3004
2355
|
const pm = await detectPackageManager(process.cwd());
|
|
3005
2356
|
const checks = [];
|
|
3006
2357
|
for (const script of present) {
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
);
|
|
3010
|
-
}
|
|
3011
|
-
return { checks };
|
|
3012
|
-
}
|
|
3013
|
-
async function registryChecks(id) {
|
|
3014
|
-
const profile = LANGUAGE_PROFILES[id];
|
|
3015
|
-
const runnable = profile.verification.filter(
|
|
3016
|
-
(spec) => !spec.requiresFile || existsSync3(join11(process.cwd(), spec.requiresFile))
|
|
3017
|
-
);
|
|
3018
|
-
if (runnable.length === 0) {
|
|
3019
|
-
return {
|
|
3020
|
-
limitation: `No mechanical verification available for ${profile.displayName} in this repo.`
|
|
3021
|
-
};
|
|
2358
|
+
const command = `${pm} run ${script}`;
|
|
2359
|
+
const { code, output } = await runCommand(pm, ["run", script]);
|
|
2360
|
+
checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
|
|
3022
2361
|
}
|
|
3023
|
-
|
|
3024
|
-
for (const spec of runnable) {
|
|
3025
|
-
checks.push(
|
|
3026
|
-
await runCheck(spec.argv.join(" "), spec.argv[0], [...spec.argv.slice(1)])
|
|
3027
|
-
);
|
|
3028
|
-
}
|
|
3029
|
-
return { checks };
|
|
3030
|
-
}
|
|
3031
|
-
async function runRepoVerificationCheck(languages = [DEFAULT_LANGUAGE_ID]) {
|
|
3032
|
-
const ids = [...new Set(languages)];
|
|
3033
|
-
if (ids.length === 0) ids.push(DEFAULT_LANGUAGE_ID);
|
|
3034
|
-
const checks = [];
|
|
3035
|
-
const limitations = [];
|
|
3036
|
-
for (const id of ids) {
|
|
3037
|
-
const result = id === JAVASCRIPT ? await javascriptChecks() : await registryChecks(id);
|
|
3038
|
-
if ("checks" in result) checks.push(...result.checks);
|
|
3039
|
-
else limitations.push(result.limitation);
|
|
3040
|
-
}
|
|
3041
|
-
if (checks.length === 0) {
|
|
3042
|
-
return {
|
|
3043
|
-
ok: false,
|
|
3044
|
-
checks: [],
|
|
3045
|
-
limitation: limitations.join(" ") || "No verification checks available."
|
|
3046
|
-
};
|
|
3047
|
-
}
|
|
3048
|
-
return {
|
|
3049
|
-
ok: checks.every((c) => c.ok),
|
|
3050
|
-
checks,
|
|
3051
|
-
...limitations.length ? { limitation: limitations.join(" ") } : {}
|
|
3052
|
-
};
|
|
2362
|
+
return { ok: checks.every((c) => c.ok), checks };
|
|
3053
2363
|
}
|
|
3054
2364
|
|
|
3055
2365
|
// src/lib/tools/verifyImplementation.ts
|
|
3056
|
-
function verifyImplementationTool(
|
|
2366
|
+
function verifyImplementationTool() {
|
|
3057
2367
|
return tool8({
|
|
3058
|
-
description: "Run the repo's mechanical verification
|
|
2368
|
+
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.",
|
|
3059
2369
|
inputSchema: z11.object(),
|
|
3060
2370
|
execute: async () => {
|
|
3061
|
-
logger.info(
|
|
3062
|
-
return runRepoVerificationCheck(
|
|
2371
|
+
logger.info("called verifyImplementation tool");
|
|
2372
|
+
return runRepoVerificationCheck();
|
|
3063
2373
|
}
|
|
3064
2374
|
});
|
|
3065
2375
|
}
|
|
@@ -3185,17 +2495,12 @@ var DEFAULT_TOOL_LIMITS = {
|
|
|
3185
2495
|
read: 20,
|
|
3186
2496
|
match: 100
|
|
3187
2497
|
};
|
|
3188
|
-
function createToolContext({
|
|
3189
|
-
limits = DEFAULT_TOOL_LIMITS,
|
|
3190
|
-
cwd = process.cwd(),
|
|
3191
|
-
languages = [DEFAULT_LANGUAGE_ID]
|
|
3192
|
-
} = {}) {
|
|
2498
|
+
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
|
|
3193
2499
|
return {
|
|
3194
2500
|
root: cwd,
|
|
3195
2501
|
cwd,
|
|
3196
2502
|
limits,
|
|
3197
|
-
counts: { list: 0, search: 0, read: 0 }
|
|
3198
|
-
languages: languages.length ? languages : [DEFAULT_LANGUAGE_ID]
|
|
2503
|
+
counts: { list: 0, search: 0, read: 0 }
|
|
3199
2504
|
};
|
|
3200
2505
|
}
|
|
3201
2506
|
|
|
@@ -3232,7 +2537,7 @@ function createTools(ctx, { output, tools }) {
|
|
|
3232
2537
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
3233
2538
|
verifyImplementation: withLogging(
|
|
3234
2539
|
"verifyImplementation",
|
|
3235
|
-
verifyImplementationTool(
|
|
2540
|
+
verifyImplementationTool()
|
|
3236
2541
|
),
|
|
3237
2542
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
3238
2543
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
@@ -3268,7 +2573,7 @@ async function runAgent(req) {
|
|
|
3268
2573
|
baseURL: PROXY_BASE_URL,
|
|
3269
2574
|
fetch: proxyFetch
|
|
3270
2575
|
});
|
|
3271
|
-
const toolContext = createToolContext(
|
|
2576
|
+
const toolContext = createToolContext();
|
|
3272
2577
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
3273
2578
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
3274
2579
|
const instructions = [
|
|
@@ -3359,11 +2664,8 @@ var detectLanguageSchema = z16.object({
|
|
|
3359
2664
|
var detectLanguage = () => runAgent({
|
|
3360
2665
|
instructions: [
|
|
3361
2666
|
"Analyze the codebase and determine the programming languages and frameworks used",
|
|
3362
|
-
"
|
|
3363
|
-
"List the language that owns the backend/data code first \u2014 that is the one an ingestion script will be written in.",
|
|
3364
|
-
"If a superset language is found, exclude the subset language. TS-over-JS. Kotlin-over-Java when Kotlin is primary.",
|
|
2667
|
+
"If a superset language is found, exclude the subset language. TS-over-JS.",
|
|
3365
2668
|
"If a meta-framework is used, exclude the framework. Next-over-React.",
|
|
3366
|
-
"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).",
|
|
3367
2669
|
"Return the exact version",
|
|
3368
2670
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
3369
2671
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
@@ -3412,7 +2714,6 @@ var MODE_CONFIG = {
|
|
|
3412
2714
|
"Analyze the codebase to find the data entities (models) that should be ingested into Algolia.",
|
|
3413
2715
|
"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).",
|
|
3414
2716
|
"Inspect the source of each entity to extract real field names for attributes \u2014 do not guess or leave attributes empty.",
|
|
3415
|
-
"Entities live wherever the stack keeps them: TypeScript interfaces or a Prisma schema, Django models.py, Rails app/models, Laravel Eloquent models, JPA @Entity classes, Go structs, C# entity classes, Pydantic models.",
|
|
3416
2717
|
"Prefer domain models (e.g. Document, Product, User) over framework or infrastructure types.",
|
|
3417
2718
|
"Use as few tools as possible, but do not guess. If you cannot find any entities, return an empty array.",
|
|
3418
2719
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
@@ -3424,9 +2725,8 @@ var MODE_CONFIG = {
|
|
|
3424
2725
|
instructions: [
|
|
3425
2726
|
"Analyze the codebase to determine the single best location to add search UI functionality.",
|
|
3426
2727
|
"Prefer a shared, always-rendered layout location (e.g. a header or navigation component) so search is reachable across the app.",
|
|
3427
|
-
"
|
|
3428
|
-
|
|
3429
|
-
'Use as few tools as possible, but do not guess. If the project renders no UI at all (an API-only service), say "unknown".',
|
|
2728
|
+
"Return one file path as searchImplementationAnalysis (e.g. /layouts/header.tsx).",
|
|
2729
|
+
'Use as few tools as possible, but do not guess. If you cannot find a clear location, say "unknown".',
|
|
3430
2730
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
3431
2731
|
"When done, call reportStatus"
|
|
3432
2732
|
],
|
|
@@ -3435,8 +2735,8 @@ var MODE_CONFIG = {
|
|
|
3435
2735
|
verification: {
|
|
3436
2736
|
instructions: [
|
|
3437
2737
|
"Analyze the codebase to determine which code-quality tools are available to validate changes.",
|
|
3438
|
-
"Look at
|
|
3439
|
-
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"]
|
|
2738
|
+
"Look at package.json scripts, config files (e.g. .eslintrc, tsconfig, prettier), and dev dependencies.",
|
|
2739
|
+
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"].',
|
|
3440
2740
|
"Use as few tools as possible, but do not guess. If you cannot find any, return an empty array.",
|
|
3441
2741
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
3442
2742
|
"When done, call reportStatus"
|
|
@@ -3463,7 +2763,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3463
2763
|
// package.json
|
|
3464
2764
|
var package_default = {
|
|
3465
2765
|
name: "@algolia/wizard",
|
|
3466
|
-
version: "0.9.0-rc.
|
|
2766
|
+
version: "0.9.0-rc.84.73",
|
|
3467
2767
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3468
2768
|
type: "module",
|
|
3469
2769
|
engines: {
|
|
@@ -3485,7 +2785,7 @@ var package_default = {
|
|
|
3485
2785
|
prepare: "husky",
|
|
3486
2786
|
prepublishOnly: "pnpm build",
|
|
3487
2787
|
reset: "tsx ./scripts/reset-state.ts",
|
|
3488
|
-
"test:
|
|
2788
|
+
"test:fixtures": "touch .env && tsx --env-file=.env ./fixtures/run-fixtures.ts",
|
|
3489
2789
|
"test:tools": "tsx ./tool-evals/toolEval.ts",
|
|
3490
2790
|
test: "vitest",
|
|
3491
2791
|
typecheck: "tsc --noEmit -p tsconfig.json"
|
|
@@ -3566,185 +2866,82 @@ function parseEntries(raw) {
|
|
|
3566
2866
|
return raw.split(",").map(clean).filter(Boolean).slice(0, MAX_ENTRIES).map((name) => ({ name, version: "unknown" }));
|
|
3567
2867
|
}
|
|
3568
2868
|
var summarize = (entries) => entries.length ? entries.map((e) => e.name).join(", ") : "none";
|
|
3569
|
-
|
|
3570
|
-
// src/actions/confirmLanguage.ts
|
|
3571
|
-
import z19 from "zod";
|
|
3572
|
-
var confirmLanguageSchema = z19.object({
|
|
3573
|
-
languages: detectLanguageSchema.shape.languages
|
|
3574
|
-
});
|
|
3575
|
-
var OTHER_OPTION = "Other";
|
|
3576
|
-
function confirmed(languages) {
|
|
3577
|
-
track("AI Wizard Language Confirmed", { languages });
|
|
3578
|
-
return { languages };
|
|
3579
|
-
}
|
|
3580
|
-
async function askOtherLanguage(ctx) {
|
|
3581
|
-
let prompt = "enter the language for your ingestion script";
|
|
2869
|
+
async function askList(ctx, prompt, { required = false } = {}) {
|
|
3582
2870
|
for (; ; ) {
|
|
3583
2871
|
const answer = await ctx.requestUserInput({
|
|
3584
2872
|
prompt,
|
|
3585
2873
|
promptType: "textInput",
|
|
3586
|
-
options: []
|
|
2874
|
+
options: [],
|
|
2875
|
+
helpText: 'Comma-separated, e.g. "TypeScript, Node".'
|
|
3587
2876
|
});
|
|
3588
2877
|
if (typeof answer !== "string") {
|
|
3589
|
-
throw new Error("
|
|
2878
|
+
throw new Error("askList received an unexpected non-text result");
|
|
3590
2879
|
}
|
|
3591
|
-
const
|
|
3592
|
-
if (
|
|
3593
|
-
prompt = "
|
|
2880
|
+
const entries = parseEntries(answer);
|
|
2881
|
+
if (entries.length || !required) return entries;
|
|
2882
|
+
prompt = "Please enter at least one entry:";
|
|
3594
2883
|
}
|
|
3595
2884
|
}
|
|
2885
|
+
|
|
2886
|
+
// src/actions/confirmLanguage.ts
|
|
2887
|
+
import z19 from "zod";
|
|
2888
|
+
var confirmLanguageSchema = z19.object({
|
|
2889
|
+
languages: detectLanguageSchema.shape.languages
|
|
2890
|
+
});
|
|
3596
2891
|
async function confirmLanguage(ctx) {
|
|
3597
2892
|
const detected = ctx.getStepOutput("project-scan");
|
|
3598
|
-
const
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
prompt: `Write the ingestion script in ${primary.name}?`,
|
|
3604
|
-
promptType: "acceptReject",
|
|
3605
|
-
options: [`Confirm ${primary.name}`, "Use a different language"],
|
|
3606
|
-
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0],
|
|
3607
|
-
messages: detectedLanguages.length > 1 ? [`Detected: ${summarize(detectedLanguages)}`] : []
|
|
3608
|
-
});
|
|
3609
|
-
if (accepted === true) return confirmed(detectedLanguages);
|
|
3610
|
-
}
|
|
3611
|
-
const options = [...CURATED_LANGUAGES];
|
|
3612
|
-
for (const language of detectedLanguages) {
|
|
3613
|
-
if (!options.some((o) => isSameLanguage(o, language.name))) {
|
|
3614
|
-
options.push(language.name);
|
|
3615
|
-
}
|
|
3616
|
-
}
|
|
3617
|
-
options.push(OTHER_OPTION);
|
|
3618
|
-
const detectedFor = (option) => detectedLanguages.find((l) => isSameLanguage(option, l.name));
|
|
3619
|
-
const secondary = options.map(
|
|
3620
|
-
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
3621
|
-
);
|
|
3622
|
-
const defaultSelectedIndex = Math.max(
|
|
3623
|
-
options.findIndex((o) => detectedFor(o)),
|
|
3624
|
-
0
|
|
3625
|
-
);
|
|
3626
|
-
const selection = await ctx.requestUserInput({
|
|
3627
|
-
prompt: "select the language for your ingestion script",
|
|
3628
|
-
promptType: "multipleChoice",
|
|
3629
|
-
options,
|
|
3630
|
-
secondary,
|
|
3631
|
-
defaultSelectedIndex
|
|
2893
|
+
const answer = await ctx.requestUserInput({
|
|
2894
|
+
prompt: "Did we detect your language(s) correctly?",
|
|
2895
|
+
promptType: "acceptReject",
|
|
2896
|
+
options: ["Yes", "No"],
|
|
2897
|
+
messages: [`Languages: ${summarize(detected.languages)}`]
|
|
3632
2898
|
});
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
}
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
2899
|
+
const languages = answer === true ? detected.languages : await askList(ctx, "List the languages your project uses:", {
|
|
2900
|
+
required: true
|
|
2901
|
+
});
|
|
2902
|
+
track("AI Wizard Language Confirmed", {
|
|
2903
|
+
languages
|
|
2904
|
+
});
|
|
2905
|
+
return { languages };
|
|
3639
2906
|
}
|
|
3640
2907
|
|
|
3641
2908
|
// src/actions/confirmFramework.ts
|
|
3642
2909
|
import z20 from "zod";
|
|
3643
|
-
|
|
3644
|
-
// src/lib/frameworks.ts
|
|
3645
|
-
var BACKEND_ONLY_FRAMEWORK = "Backend only / API";
|
|
3646
|
-
var FRAMEWORKS = [
|
|
3647
|
-
// Frontend — InstantSearch component flavors.
|
|
3648
|
-
{ name: "Next.js", strategy: "react", aliases: ["next", "nextjs"] },
|
|
3649
|
-
{ name: "React", strategy: "react", aliases: ["reactjs"] },
|
|
3650
|
-
{ name: "Vue", strategy: "vue", aliases: ["vuejs", "nuxt", "nuxtjs"] },
|
|
3651
|
-
{ name: "Angular", strategy: "angular", aliases: ["angularjs"] },
|
|
3652
|
-
// No Svelte InstantSearch flavor exists, so it uses InstantSearch.js.
|
|
3653
|
-
{ name: "Svelte", strategy: "js", aliases: ["sveltekit"] },
|
|
3654
|
-
{
|
|
3655
|
-
name: "Vanilla JS",
|
|
3656
|
-
strategy: "js",
|
|
3657
|
-
aliases: ["vanilla", "javascript", "js", "astro", "vite"]
|
|
3658
|
-
},
|
|
3659
|
-
// Backend — Algolia's official framework integrations. Server-rendered
|
|
3660
|
-
// templates get InstantSearch.js from a CDN.
|
|
3661
|
-
{
|
|
3662
|
-
name: "Rails",
|
|
3663
|
-
strategy: "cdn-template",
|
|
3664
|
-
aliases: ["rubyonrails", "ruby on rails", "erb"]
|
|
3665
|
-
},
|
|
3666
|
-
{ name: "Django", strategy: "cdn-template", aliases: ["jinja", "jinja2"] },
|
|
3667
|
-
{ name: "Laravel", strategy: "cdn-template", aliases: ["blade"] },
|
|
3668
|
-
{ name: "Symfony", strategy: "cdn-template", aliases: ["twig"] },
|
|
3669
|
-
// Mobile — Algolia ships InstantSearch iOS/Android and Dart clients, but the
|
|
3670
|
-
// wizard can't scaffold a native UI, so it points at the docs instead.
|
|
3671
|
-
{ name: "Flutter", strategy: "none", aliases: [] },
|
|
3672
|
-
{ name: "iOS", strategy: "none", aliases: ["swiftui", "uikit"] },
|
|
3673
|
-
{ name: "Android", strategy: "none", aliases: ["jetpack compose", "compose"] },
|
|
3674
|
-
{ name: BACKEND_ONLY_FRAMEWORK, strategy: "cdn-template", aliases: [] }
|
|
3675
|
-
];
|
|
3676
|
-
var CURATED_FRAMEWORKS = FRAMEWORKS.map(
|
|
3677
|
-
(f) => f.name
|
|
3678
|
-
);
|
|
3679
|
-
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3680
|
-
var ALIAS_TO_NAME = /* @__PURE__ */ new Map();
|
|
3681
|
-
for (const framework of FRAMEWORKS) {
|
|
3682
|
-
for (const alias of [framework.name, ...framework.aliases]) {
|
|
3683
|
-
ALIAS_TO_NAME.set(normalize(alias), framework.name);
|
|
3684
|
-
}
|
|
3685
|
-
}
|
|
3686
|
-
var STRATEGY_BY_NAME = new Map(FRAMEWORKS.map((f) => [f.name, f.strategy]));
|
|
3687
|
-
function canonicalFrameworkName(name) {
|
|
3688
|
-
return ALIAS_TO_NAME.get(normalize(name));
|
|
3689
|
-
}
|
|
3690
|
-
function isSameFramework(a, b) {
|
|
3691
|
-
const x = canonicalFrameworkName(a) ?? normalize(a);
|
|
3692
|
-
const y = canonicalFrameworkName(b) ?? normalize(b);
|
|
3693
|
-
return x !== "" && x === y;
|
|
3694
|
-
}
|
|
3695
|
-
function resolveSearchStrategy(frameworkName, hasJavaScriptInStack) {
|
|
3696
|
-
const canonical = frameworkName ? canonicalFrameworkName(frameworkName) : void 0;
|
|
3697
|
-
const strategy = canonical ? STRATEGY_BY_NAME.get(canonical) : void 0;
|
|
3698
|
-
if (strategy) return strategy;
|
|
3699
|
-
return hasJavaScriptInStack ? "js" : "cdn-template";
|
|
3700
|
-
}
|
|
3701
|
-
function searchDocKey(strategy) {
|
|
3702
|
-
return strategy === "cdn-template" ? "templates" : strategy;
|
|
3703
|
-
}
|
|
3704
|
-
function bundlesJavaScript(strategy) {
|
|
3705
|
-
return strategy !== "cdn-template" && strategy !== "none";
|
|
3706
|
-
}
|
|
3707
|
-
function canScaffoldSearchUI(strategy) {
|
|
3708
|
-
return strategy !== "none";
|
|
3709
|
-
}
|
|
3710
|
-
var ENV_PREFIXES = [
|
|
3711
|
-
{ aliases: ["next", "nextjs"], prefix: "NEXT_PUBLIC_" },
|
|
3712
|
-
{ aliases: ["nuxt", "nuxtjs"], prefix: "NUXT_PUBLIC_" },
|
|
3713
|
-
{ aliases: ["astro"], prefix: "PUBLIC_" },
|
|
3714
|
-
{ aliases: ["vite"], prefix: "VITE_" }
|
|
3715
|
-
];
|
|
3716
|
-
var DEFAULT_ENV_PREFIX = "PUBLIC_";
|
|
3717
|
-
function publicEnvPrefix(frameworkNames, strategy) {
|
|
3718
|
-
if (!bundlesJavaScript(strategy)) return "";
|
|
3719
|
-
const present = new Set(frameworkNames.map(normalize));
|
|
3720
|
-
for (const { aliases, prefix } of ENV_PREFIXES) {
|
|
3721
|
-
if (aliases.some((alias) => present.has(alias))) return prefix;
|
|
3722
|
-
}
|
|
3723
|
-
return DEFAULT_ENV_PREFIX;
|
|
3724
|
-
}
|
|
3725
|
-
function describeSearchTarget(strategy, frameworkName) {
|
|
3726
|
-
switch (strategy) {
|
|
3727
|
-
case "react":
|
|
3728
|
-
return "React (react-instantsearch)";
|
|
3729
|
-
case "vue":
|
|
3730
|
-
return "Vue (vue-instantsearch)";
|
|
3731
|
-
case "angular":
|
|
3732
|
-
return "Angular (angular-instantsearch)";
|
|
3733
|
-
case "js":
|
|
3734
|
-
return "plain JavaScript (InstantSearch.js)";
|
|
3735
|
-
case "cdn-template":
|
|
3736
|
-
return `${frameworkName ?? "server-rendered"} templates (InstantSearch.js via CDN)`;
|
|
3737
|
-
case "none":
|
|
3738
|
-
return frameworkName ?? "a native mobile app";
|
|
3739
|
-
}
|
|
3740
|
-
}
|
|
3741
|
-
|
|
3742
|
-
// src/actions/confirmFramework.ts
|
|
3743
2910
|
var confirmFrameworkSchema = z20.object({
|
|
3744
2911
|
frameworks: detectLanguageSchema.shape.frameworks
|
|
3745
2912
|
});
|
|
3746
|
-
var
|
|
3747
|
-
|
|
2913
|
+
var CURATED_FRAMEWORKS = [
|
|
2914
|
+
"Next.js",
|
|
2915
|
+
"React",
|
|
2916
|
+
"Vue",
|
|
2917
|
+
"Angular",
|
|
2918
|
+
"Svelte",
|
|
2919
|
+
"Vanilla JS"
|
|
2920
|
+
];
|
|
2921
|
+
var OTHER_OPTION = "Other";
|
|
2922
|
+
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2923
|
+
var FRAMEWORK_ALIASES = {
|
|
2924
|
+
next: "nextjs",
|
|
2925
|
+
nextjs: "nextjs",
|
|
2926
|
+
react: "react",
|
|
2927
|
+
reactjs: "react",
|
|
2928
|
+
vue: "vue",
|
|
2929
|
+
vuejs: "vue",
|
|
2930
|
+
angular: "angular",
|
|
2931
|
+
angularjs: "angular",
|
|
2932
|
+
svelte: "svelte",
|
|
2933
|
+
sveltekit: "svelte",
|
|
2934
|
+
vanillajs: "vanillajs",
|
|
2935
|
+
vanilla: "vanillajs",
|
|
2936
|
+
javascript: "vanillajs",
|
|
2937
|
+
js: "vanillajs"
|
|
2938
|
+
};
|
|
2939
|
+
var isSameFramework = (a, b) => {
|
|
2940
|
+
const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
|
|
2941
|
+
const y = FRAMEWORK_ALIASES[normalize(b)] ?? normalize(b);
|
|
2942
|
+
return x !== "" && x === y;
|
|
2943
|
+
};
|
|
2944
|
+
function confirmed(name, version) {
|
|
3748
2945
|
const frameworks = [{ name, version: version ?? "unknown" }];
|
|
3749
2946
|
track("AI Wizard Frontend Framework Confirmed", { frameworks });
|
|
3750
2947
|
return { frameworks };
|
|
@@ -3772,7 +2969,7 @@ async function confirmFramework(ctx) {
|
|
|
3772
2969
|
for (const fw of detectedFrameworks) {
|
|
3773
2970
|
if (!options.some((o) => isSameFramework(o, fw.name))) options.push(fw.name);
|
|
3774
2971
|
}
|
|
3775
|
-
options.push(
|
|
2972
|
+
options.push(OTHER_OPTION);
|
|
3776
2973
|
const detectedFor = (option) => detectedFrameworks.find((fw) => isSameFramework(option, fw.name));
|
|
3777
2974
|
const primary = detectedFrameworks[0];
|
|
3778
2975
|
if (primary) {
|
|
@@ -3782,7 +2979,7 @@ async function confirmFramework(ctx) {
|
|
|
3782
2979
|
options: [`Confirm ${primary.name}`, "Use a different framework"],
|
|
3783
2980
|
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0]
|
|
3784
2981
|
});
|
|
3785
|
-
if (accepted === true) return
|
|
2982
|
+
if (accepted === true) return confirmed(primary.name, primary.version);
|
|
3786
2983
|
}
|
|
3787
2984
|
const secondary = options.map(
|
|
3788
2985
|
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
@@ -3792,7 +2989,7 @@ async function confirmFramework(ctx) {
|
|
|
3792
2989
|
0
|
|
3793
2990
|
);
|
|
3794
2991
|
const selection = await ctx.requestUserInput({
|
|
3795
|
-
prompt: "select
|
|
2992
|
+
prompt: "select a framework",
|
|
3796
2993
|
promptType: "multipleChoice",
|
|
3797
2994
|
options,
|
|
3798
2995
|
secondary,
|
|
@@ -3801,10 +2998,10 @@ async function confirmFramework(ctx) {
|
|
|
3801
2998
|
if (typeof selection !== "string") {
|
|
3802
2999
|
throw new Error("confirmFramework received an unexpected non-text result");
|
|
3803
3000
|
}
|
|
3804
|
-
if (selection ===
|
|
3805
|
-
return
|
|
3001
|
+
if (selection === OTHER_OPTION) {
|
|
3002
|
+
return confirmed(await askOtherFramework(ctx));
|
|
3806
3003
|
}
|
|
3807
|
-
return
|
|
3004
|
+
return confirmed(selection, detectedFor(selection)?.version);
|
|
3808
3005
|
}
|
|
3809
3006
|
|
|
3810
3007
|
// src/actions/promptUser.ts
|
|
@@ -3897,15 +3094,15 @@ async function confirmEntities(ctx) {
|
|
|
3897
3094
|
onSubmit: () => {
|
|
3898
3095
|
}
|
|
3899
3096
|
});
|
|
3900
|
-
const
|
|
3901
|
-
if (
|
|
3097
|
+
const confirmed2 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
|
|
3098
|
+
if (confirmed2.length === 0) {
|
|
3902
3099
|
throw new Error("User cancelled entity selection \u2014 analysis halted.");
|
|
3903
3100
|
}
|
|
3904
|
-
ctx.setUserInput("confirmedEntities",
|
|
3101
|
+
ctx.setUserInput("confirmedEntities", confirmed2);
|
|
3905
3102
|
track("AI Wizard Entities Confirmed", {
|
|
3906
|
-
entities: toEntitySummary(
|
|
3103
|
+
entities: toEntitySummary(confirmed2)
|
|
3907
3104
|
});
|
|
3908
|
-
return { ingestionAnalysis: entities, confirmedEntities:
|
|
3105
|
+
return { ingestionAnalysis: entities, confirmedEntities: confirmed2 };
|
|
3909
3106
|
}
|
|
3910
3107
|
|
|
3911
3108
|
// src/actions/review.ts
|
|
@@ -3929,7 +3126,7 @@ ${JSON.stringify(s.output, null, 2)}`
|
|
|
3929
3126
|
}
|
|
3930
3127
|
function formatReviewSummary(result) {
|
|
3931
3128
|
const nextStepLines = result.nextSteps.map((step) => {
|
|
3932
|
-
const isIngestCommand = step.includes("algolia-wizard/
|
|
3129
|
+
const isIngestCommand = step.includes(".algolia-wizard/ingest.sh");
|
|
3933
3130
|
const isWorktreeCommand = step.includes("/worktrees/");
|
|
3934
3131
|
return {
|
|
3935
3132
|
text: `\u2192 ${step}`,
|
|
@@ -3971,14 +3168,13 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3971
3168
|
import z24 from "zod";
|
|
3972
3169
|
|
|
3973
3170
|
// src/lib/worktree.ts
|
|
3974
|
-
import { execFile } from "node:child_process";
|
|
3975
|
-
import {
|
|
3976
|
-
import { copyFile, mkdir as mkdir6, readdir as readdir4, readFile as readFile9, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3171
|
+
import { execFile, spawn as spawn3 } from "node:child_process";
|
|
3172
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile8, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3977
3173
|
import {
|
|
3978
3174
|
basename as basename2,
|
|
3979
3175
|
dirname as dirname7,
|
|
3980
3176
|
isAbsolute as isAbsolute2,
|
|
3981
|
-
join as
|
|
3177
|
+
join as join10,
|
|
3982
3178
|
relative as relative2,
|
|
3983
3179
|
resolve as resolve3
|
|
3984
3180
|
} from "node:path";
|
|
@@ -4012,8 +3208,8 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
4012
3208
|
return out.trim().length > 0;
|
|
4013
3209
|
}
|
|
4014
3210
|
async function pruneOldWorktrees(repoRoot) {
|
|
4015
|
-
const dir =
|
|
4016
|
-
const stale = (await
|
|
3211
|
+
const dir = join10(stateDir(repoRoot), "worktrees");
|
|
3212
|
+
const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
4017
3213
|
for (const slug of stale) {
|
|
4018
3214
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
4019
3215
|
try {
|
|
@@ -4023,7 +3219,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
4023
3219
|
"worktree",
|
|
4024
3220
|
"remove",
|
|
4025
3221
|
"--force",
|
|
4026
|
-
|
|
3222
|
+
join10(dir, slug)
|
|
4027
3223
|
]);
|
|
4028
3224
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
4029
3225
|
} catch (err) {
|
|
@@ -4037,55 +3233,43 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
4037
3233
|
async function createWorktree(repoRoot) {
|
|
4038
3234
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
4039
3235
|
const dirSlug = branch.replace(/\//g, "-");
|
|
4040
|
-
const path =
|
|
3236
|
+
const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
|
|
4041
3237
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
4042
3238
|
await pruneOldWorktrees(repoRoot);
|
|
4043
3239
|
await mkdir6(dirname7(path), { recursive: true });
|
|
4044
3240
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
4045
3241
|
return { path, branch };
|
|
4046
3242
|
}
|
|
4047
|
-
async function
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
return { ok: code === 0, output: output.trim() };
|
|
4053
|
-
}
|
|
4054
|
-
async function installWorktreeDeps(worktreePath, toolchain) {
|
|
4055
|
-
const { profile, installSteps, packageManager } = toolchain;
|
|
4056
|
-
const declared = packageManager.dependency.mode === "agent-declares" ? packageManager.dependency.file : void 0;
|
|
4057
|
-
const haveSomethingToInstall = await hasProfileManifest(worktreePath, profile) || declared !== void 0 && existsSync4(join12(worktreePath, declared));
|
|
4058
|
-
if (!haveSomethingToInstall) {
|
|
4059
|
-
return {
|
|
4060
|
-
ok: true,
|
|
4061
|
-
output: `no ${profile.displayName} manifest; skipped install`
|
|
4062
|
-
};
|
|
4063
|
-
}
|
|
4064
|
-
if (installSteps.length === 0) {
|
|
4065
|
-
return {
|
|
4066
|
-
ok: true,
|
|
4067
|
-
output: `${profile.displayName} (${toolchain.packageManager.id}) has no wizard-run install step`
|
|
4068
|
-
};
|
|
4069
|
-
}
|
|
4070
|
-
const outputs = [];
|
|
4071
|
-
for (const step of installSteps) {
|
|
4072
|
-
if (step.requiresFile && !existsSync4(join12(worktreePath, step.requiresFile)))
|
|
4073
|
-
continue;
|
|
4074
|
-
const result = await spawnStep(worktreePath, step.argv);
|
|
4075
|
-
if (result.output) outputs.push(result.output);
|
|
4076
|
-
if (result.ok) continue;
|
|
4077
|
-
if (step.optional) {
|
|
4078
|
-
logger.warn(
|
|
4079
|
-
{ step: step.argv.join(" "), output: result.output },
|
|
4080
|
-
"installWorktreeDeps: optional install step failed; continuing"
|
|
4081
|
-
);
|
|
4082
|
-
continue;
|
|
4083
|
-
}
|
|
4084
|
-
return { ok: false, output: outputs.join("\n").trim() };
|
|
3243
|
+
async function installWorktreeDeps(worktreePath) {
|
|
3244
|
+
try {
|
|
3245
|
+
await readPackageJson(worktreePath);
|
|
3246
|
+
} catch {
|
|
3247
|
+
return { ok: true, output: "no package.json; skipped install" };
|
|
4085
3248
|
}
|
|
4086
|
-
|
|
3249
|
+
const pm = await detectPackageManager(worktreePath);
|
|
3250
|
+
return new Promise((resolve4) => {
|
|
3251
|
+
let output = "";
|
|
3252
|
+
const child = spawn3(pm, ["install"], {
|
|
3253
|
+
cwd: worktreePath,
|
|
3254
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3255
|
+
});
|
|
3256
|
+
child.stdout?.on("data", (d) => output += d);
|
|
3257
|
+
child.stderr?.on("data", (d) => output += d);
|
|
3258
|
+
child.on(
|
|
3259
|
+
"error",
|
|
3260
|
+
(err) => resolve4({
|
|
3261
|
+
ok: false,
|
|
3262
|
+
output: `Failed to run ${pm} install: ${err.message}`
|
|
3263
|
+
})
|
|
3264
|
+
);
|
|
3265
|
+
child.on(
|
|
3266
|
+
"close",
|
|
3267
|
+
(code) => resolve4({ ok: code === 0, output: output.trim() })
|
|
3268
|
+
);
|
|
3269
|
+
});
|
|
4087
3270
|
}
|
|
4088
|
-
|
|
3271
|
+
var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
|
|
3272
|
+
function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
4089
3273
|
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
4090
3274
|
return {
|
|
4091
3275
|
ok: false,
|
|
@@ -4100,29 +3284,18 @@ function validateIngestEntrypoint(worktreePath, entrypoint, allowedExtensions) {
|
|
|
4100
3284
|
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
4101
3285
|
};
|
|
4102
3286
|
}
|
|
4103
|
-
if (allowedExtensions?.length && !allowedExtensions.some((ext) => entrypoint.endsWith(ext))) {
|
|
4104
|
-
return {
|
|
4105
|
-
ok: false,
|
|
4106
|
-
reason: `entrypoint "${entrypoint}" is not one of ${allowedExtensions.join(", ")}`
|
|
4107
|
-
};
|
|
4108
|
-
}
|
|
4109
3287
|
return { ok: true, target };
|
|
4110
3288
|
}
|
|
4111
|
-
async function runIngestScript(worktreePath,
|
|
4112
|
-
|
|
4113
|
-
if (ingest.kind !== "auto") {
|
|
3289
|
+
async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
3290
|
+
if (!INGEST_RUNTIMES.includes(runtime)) {
|
|
4114
3291
|
return {
|
|
4115
3292
|
ran: false,
|
|
4116
3293
|
ok: false,
|
|
4117
3294
|
output: "",
|
|
4118
|
-
reason:
|
|
3295
|
+
reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
|
|
4119
3296
|
};
|
|
4120
3297
|
}
|
|
4121
|
-
const validated = validateIngestEntrypoint(
|
|
4122
|
-
worktreePath,
|
|
4123
|
-
entrypoint,
|
|
4124
|
-
ingest.entrypointExtensions
|
|
4125
|
-
);
|
|
3298
|
+
const validated = validateIngestEntrypoint(worktreePath, entrypoint);
|
|
4126
3299
|
if (!validated.ok) {
|
|
4127
3300
|
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
4128
3301
|
}
|
|
@@ -4143,13 +3316,29 @@ async function runIngestScript(worktreePath, toolchain, entrypoint, env = {}) {
|
|
|
4143
3316
|
reason: `entrypoint "${entrypoint}" does not exist`
|
|
4144
3317
|
};
|
|
4145
3318
|
}
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
3319
|
+
return new Promise((resolveRun) => {
|
|
3320
|
+
let output = "";
|
|
3321
|
+
const child = spawn3(runtime, [entrypoint], {
|
|
3322
|
+
cwd: worktreePath,
|
|
3323
|
+
shell: false,
|
|
3324
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3325
|
+
env: { ...process.env, ...env }
|
|
3326
|
+
});
|
|
3327
|
+
child.stdout?.on("data", (d) => output += d);
|
|
3328
|
+
child.stderr?.on("data", (d) => output += d);
|
|
3329
|
+
child.on(
|
|
3330
|
+
"error",
|
|
3331
|
+
(err) => resolveRun({
|
|
3332
|
+
ran: true,
|
|
3333
|
+
ok: false,
|
|
3334
|
+
output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
|
|
3335
|
+
})
|
|
3336
|
+
);
|
|
3337
|
+
child.on(
|
|
3338
|
+
"close",
|
|
3339
|
+
(code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
|
|
3340
|
+
);
|
|
4151
3341
|
});
|
|
4152
|
-
return { ran: true, ok: code === 0, output: output.trim() };
|
|
4153
3342
|
}
|
|
4154
3343
|
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
4155
3344
|
const trimmed = sourcePath.trim();
|
|
@@ -4164,8 +3353,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
4164
3353
|
} catch {
|
|
4165
3354
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
4166
3355
|
}
|
|
4167
|
-
const relPath =
|
|
4168
|
-
const dest =
|
|
3356
|
+
const relPath = join10(ingestDir, basename2(source));
|
|
3357
|
+
const dest = join10(worktreePath, relPath);
|
|
4169
3358
|
try {
|
|
4170
3359
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
4171
3360
|
await copyFile(source, dest);
|
|
@@ -4181,10 +3370,10 @@ function hasEnvVar(content, name) {
|
|
|
4181
3370
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
4182
3371
|
}
|
|
4183
3372
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
4184
|
-
const target =
|
|
3373
|
+
const target = join10(worktreePath, ".env");
|
|
4185
3374
|
let existing = "";
|
|
4186
3375
|
try {
|
|
4187
|
-
existing = await
|
|
3376
|
+
existing = await readFile8(target, "utf8");
|
|
4188
3377
|
} catch (err) {
|
|
4189
3378
|
if (err.code !== "ENOENT") throw err;
|
|
4190
3379
|
}
|
|
@@ -4301,33 +3490,69 @@ async function resolveSearchOnlyKey(index) {
|
|
|
4301
3490
|
}
|
|
4302
3491
|
|
|
4303
3492
|
// src/lib/algoliaDocs.ts
|
|
4304
|
-
import { readFileSync, existsSync as
|
|
4305
|
-
import { dirname as dirname8, join as
|
|
3493
|
+
import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
|
|
3494
|
+
import { dirname as dirname8, join as join11 } from "node:path";
|
|
4306
3495
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4307
|
-
var DOCS_SUBPATH =
|
|
3496
|
+
var DOCS_SUBPATH = join11("docs", "algolia-sdk");
|
|
4308
3497
|
function findDocsDir() {
|
|
4309
3498
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
4310
3499
|
for (; ; ) {
|
|
4311
|
-
const candidate =
|
|
4312
|
-
if (
|
|
3500
|
+
const candidate = join11(dir, DOCS_SUBPATH);
|
|
3501
|
+
if (existsSync2(candidate)) return candidate;
|
|
4313
3502
|
const parent = dirname8(dir);
|
|
4314
3503
|
if (parent === dir) return void 0;
|
|
4315
3504
|
dir = parent;
|
|
4316
3505
|
}
|
|
4317
3506
|
}
|
|
4318
|
-
function
|
|
3507
|
+
function loadAlgoliaDoc(language) {
|
|
3508
|
+
const docsDir = findDocsDir();
|
|
3509
|
+
if (!docsDir) {
|
|
3510
|
+
logger.warn(
|
|
3511
|
+
"algoliaDocs: docs/algolia-sdk not found; skipping SDK reference"
|
|
3512
|
+
);
|
|
3513
|
+
return "";
|
|
3514
|
+
}
|
|
3515
|
+
const files = readdirSync(docsDir).filter((f) => f.includes(language));
|
|
3516
|
+
if (files.length === 0) {
|
|
3517
|
+
logger.warn(
|
|
3518
|
+
{ language },
|
|
3519
|
+
"algoliaDocs: no SDK reference found for language; skipping"
|
|
3520
|
+
);
|
|
3521
|
+
return "";
|
|
3522
|
+
}
|
|
3523
|
+
return readFileSync(join11(docsDir, files[0]), "utf8").trim();
|
|
3524
|
+
}
|
|
3525
|
+
function getNamedDoc(name, language) {
|
|
4319
3526
|
const docsDir = findDocsDir();
|
|
4320
3527
|
if (!docsDir) {
|
|
4321
3528
|
logger.warn("docs/algolia-sdk not found");
|
|
4322
3529
|
return "";
|
|
4323
3530
|
}
|
|
4324
|
-
const file =
|
|
4325
|
-
if (!
|
|
4326
|
-
logger.warn({ name,
|
|
3531
|
+
const file = join11(docsDir, `${name}-${language}.md`);
|
|
3532
|
+
if (!existsSync2(file)) {
|
|
3533
|
+
logger.warn({ name, language }, "named SDK reference not found");
|
|
4327
3534
|
return "";
|
|
4328
3535
|
}
|
|
4329
3536
|
return readFileSync(file, "utf8").trim();
|
|
4330
3537
|
}
|
|
3538
|
+
function getFrameworkSpecificDoc(frameworks) {
|
|
3539
|
+
const fw = frameworks.map((f) => f.toLowerCase());
|
|
3540
|
+
if (fw.includes("vue") || fw.includes("nuxt")) {
|
|
3541
|
+
return loadAlgoliaDoc("vue");
|
|
3542
|
+
}
|
|
3543
|
+
if (fw.includes("react") || fw.includes("next.js")) {
|
|
3544
|
+
return loadAlgoliaDoc("react");
|
|
3545
|
+
}
|
|
3546
|
+
if (fw.includes("angular")) {
|
|
3547
|
+
return loadAlgoliaDoc("angular");
|
|
3548
|
+
}
|
|
3549
|
+
return loadAlgoliaDoc("js");
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3552
|
+
// src/lib/shell.ts
|
|
3553
|
+
function shellQuote(value) {
|
|
3554
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
3555
|
+
}
|
|
4331
3556
|
|
|
4332
3557
|
// src/actions/implement.ts
|
|
4333
3558
|
var implementSchema = z24.object({
|
|
@@ -4362,11 +3587,12 @@ var implementSchema = z24.object({
|
|
|
4362
3587
|
});
|
|
4363
3588
|
var implementationOutputSchema = z24.object({
|
|
4364
3589
|
summary: z24.string(),
|
|
4365
|
-
// Ingestion only:
|
|
4366
|
-
//
|
|
4367
|
-
//
|
|
4368
|
-
//
|
|
4369
|
-
//
|
|
3590
|
+
// Ingestion only: how to run the generated script, as a structured pair the
|
|
3591
|
+
// wizard turns into an argv (`<runtime> <entrypoint>`) — never a free-form
|
|
3592
|
+
// command string. `runtime` is constrained to an allowlisted interpreter and
|
|
3593
|
+
// `entrypoint` is validated to a worktree-relative path before execution, so
|
|
3594
|
+
// the agent cannot inject extra commands or swap the interpreter.
|
|
3595
|
+
runtime: z24.enum(INGEST_RUNTIMES).optional(),
|
|
4370
3596
|
entrypoint: z24.string().optional()
|
|
4371
3597
|
});
|
|
4372
3598
|
var verificationOutputSchema = z24.object({
|
|
@@ -4376,11 +3602,47 @@ var verificationOutputSchema = z24.object({
|
|
|
4376
3602
|
});
|
|
4377
3603
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
4378
3604
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
3605
|
+
var INGEST_DIR = ".algolia-wizard";
|
|
3606
|
+
function detectUiFramework(language) {
|
|
3607
|
+
const names = language.frameworks.map((f) => f.name.toLowerCase());
|
|
3608
|
+
if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
|
|
3609
|
+
if (names.some((n) => n.includes("react") || n.includes("next")))
|
|
3610
|
+
return "React";
|
|
3611
|
+
if (names.some((n) => n.includes("angular"))) return "Angular";
|
|
3612
|
+
return "JavaScript";
|
|
3613
|
+
}
|
|
3614
|
+
function frameworksForDoc(framework) {
|
|
3615
|
+
switch (framework) {
|
|
3616
|
+
case "React":
|
|
3617
|
+
return ["react"];
|
|
3618
|
+
case "Vue":
|
|
3619
|
+
return ["vue"];
|
|
3620
|
+
case "Angular":
|
|
3621
|
+
return ["angular"];
|
|
3622
|
+
case "JavaScript":
|
|
3623
|
+
return [];
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
function publicEnvPrefix(language) {
|
|
3627
|
+
const frameworkNames = language.frameworks.map(
|
|
3628
|
+
(framework) => framework.name.toLowerCase()
|
|
4383
3629
|
);
|
|
3630
|
+
if (frameworkNames.some((name) => name.includes("next"))) {
|
|
3631
|
+
return "NEXT_PUBLIC_";
|
|
3632
|
+
}
|
|
3633
|
+
if (frameworkNames.some((name) => name.includes("nuxt"))) {
|
|
3634
|
+
return "NUXT_PUBLIC_";
|
|
3635
|
+
}
|
|
3636
|
+
if (frameworkNames.some((name) => name.includes("astro"))) {
|
|
3637
|
+
return "PUBLIC_";
|
|
3638
|
+
}
|
|
3639
|
+
if (frameworkNames.some((name) => name.includes("vite"))) {
|
|
3640
|
+
return "VITE_";
|
|
3641
|
+
}
|
|
3642
|
+
return "PUBLIC_";
|
|
3643
|
+
}
|
|
3644
|
+
function searchEnvVars(language, appId, searchKey) {
|
|
3645
|
+
const prefix = publicEnvPrefix(language);
|
|
4384
3646
|
return [
|
|
4385
3647
|
{
|
|
4386
3648
|
name: `${prefix}ALGOLIA_APP_ID`,
|
|
@@ -4392,38 +3654,6 @@ function buildSearchEnvVars(language, strategy, appId, searchKey) {
|
|
|
4392
3654
|
}
|
|
4393
3655
|
];
|
|
4394
3656
|
}
|
|
4395
|
-
async function resolveIngestionProfile(ctx, language, repoRoot) {
|
|
4396
|
-
const { candidates, confirmed: confirmed3, onDisk } = await pickIngestionCandidates(
|
|
4397
|
-
repoRoot,
|
|
4398
|
-
language.languages.map((l) => l.name)
|
|
4399
|
-
);
|
|
4400
|
-
if (candidates.length === 0) {
|
|
4401
|
-
const chosen = confirmed3[0] ?? onDisk[0] ?? LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID];
|
|
4402
|
-
logger.warn(
|
|
4403
|
-
{
|
|
4404
|
-
confirmed: language.languages.map((l) => l.name),
|
|
4405
|
-
onDisk: onDisk.map((p) => p.id),
|
|
4406
|
-
chosen: chosen.id
|
|
4407
|
-
},
|
|
4408
|
-
"implement: no confirmed language matched a manifest on disk; falling back"
|
|
4409
|
-
);
|
|
4410
|
-
return chosen;
|
|
4411
|
-
}
|
|
4412
|
-
if (candidates.length === 1) return candidates[0];
|
|
4413
|
-
const backends = candidates.filter(isBackendLanguage);
|
|
4414
|
-
if (backends.length === 1) return backends[0];
|
|
4415
|
-
if (backends.length === 0) return candidates[0];
|
|
4416
|
-
if (isBackendLanguage(candidates[0])) return candidates[0];
|
|
4417
|
-
const options = backends.map((p) => p.displayName);
|
|
4418
|
-
const selection = await ctx.requestUserInput({
|
|
4419
|
-
prompt: "Which language should the ingestion script use?",
|
|
4420
|
-
promptType: "multipleChoice",
|
|
4421
|
-
options,
|
|
4422
|
-
defaultSelectedIndex: 0
|
|
4423
|
-
});
|
|
4424
|
-
const picked = typeof selection === "string" ? backends.find((p) => p.displayName === selection) : void 0;
|
|
4425
|
-
return picked ?? backends[0];
|
|
4426
|
-
}
|
|
4427
3657
|
function baseInstructions(input) {
|
|
4428
3658
|
return [
|
|
4429
3659
|
`Target Algolia index: ${input.targetIndex}`,
|
|
@@ -4451,48 +3681,37 @@ function sourceSpecificInstructions(input) {
|
|
|
4451
3681
|
generated: [
|
|
4452
3682
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
4453
3683
|
"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.",
|
|
4454
|
-
"In the script, read and parse each returned
|
|
3684
|
+
"In the script, read and parse each returned file path at runtime (e.g. JSON.parse(readFileSync(...)) in Node/Bun, json.load(open(...)) in Python) instead of inlining the records as literals.",
|
|
4455
3685
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
4456
3686
|
]
|
|
4457
3687
|
};
|
|
4458
3688
|
return byLine[input.ingestionSource];
|
|
4459
3689
|
}
|
|
4460
3690
|
function ingestionInstructions(input) {
|
|
4461
|
-
const { ingestionProfile: profile, toolchain } = input;
|
|
4462
|
-
const { ingest } = toolchain;
|
|
4463
|
-
const extensions = ingest.entrypointExtensions.join(", ");
|
|
4464
|
-
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` : ""}.`;
|
|
4465
3691
|
return [
|
|
4466
3692
|
...input.confirmed && input.confirmed.length ? [
|
|
4467
|
-
`
|
|
3693
|
+
`Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
|
|
4468
3694
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
4469
|
-
`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.
|
|
4470
|
-
|
|
3695
|
+
`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.`,
|
|
3696
|
+
"Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
|
|
4471
3697
|
"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.",
|
|
4472
|
-
getNamedDoc("save-records",
|
|
4473
|
-
|
|
3698
|
+
getNamedDoc("save-records", "js"),
|
|
3699
|
+
'Add algoliasearch to package.json "dependencies" with a valid version range; the wizard installs the worktree deps after you finish.',
|
|
4474
3700
|
"The summary should be extremely concise.",
|
|
4475
|
-
|
|
3701
|
+
`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.`,
|
|
4476
3702
|
...sourceSpecificInstructions(input)
|
|
4477
3703
|
] : []
|
|
4478
3704
|
];
|
|
4479
3705
|
}
|
|
4480
3706
|
function searchInstructions(input) {
|
|
4481
|
-
const doc =
|
|
4482
|
-
"instantsearch-setup",
|
|
4483
|
-
searchDocKey(input.searchStrategy)
|
|
4484
|
-
);
|
|
4485
|
-
const isTemplate = input.searchStrategy === "cdn-template";
|
|
4486
|
-
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.`;
|
|
3707
|
+
const doc = getFrameworkSpecificDoc(frameworksForDoc(input.uiFramework));
|
|
4487
3708
|
return [
|
|
4488
3709
|
"Implement an in-app Algolia search experience.",
|
|
4489
|
-
`Build the search UI for ${
|
|
4490
|
-
"Follow the Algolia reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3710
|
+
`Build the search UI for ${input.uiFramework}.`,
|
|
3711
|
+
"Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
4491
3712
|
doc,
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
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.',
|
|
4495
|
-
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.",
|
|
3713
|
+
`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 "${input.targetIndex}" index.`,
|
|
3714
|
+
"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.",
|
|
4496
3715
|
// appId always resolves (loadActiveProfile throws otherwise); only the
|
|
4497
3716
|
// search-only key is best-effort and can fall back to a placeholder.
|
|
4498
3717
|
`Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
@@ -4500,22 +3719,20 @@ function searchInstructions(input) {
|
|
|
4500
3719
|
// resolved app id / search-only key into ".env" under these exact names
|
|
4501
3720
|
// right after this step, so a renamed prefix here would leave the code
|
|
4502
3721
|
// reading a var the wizard never wrote.
|
|
4503
|
-
`Use exactly these env var names: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3722
|
+
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3723
|
+
'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.',
|
|
4504
3724
|
"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."
|
|
4505
3725
|
];
|
|
4506
3726
|
}
|
|
4507
3727
|
function verificationInstructions(input) {
|
|
4508
|
-
const protectedDirs = [
|
|
4509
|
-
.../* @__PURE__ */ new Set([input.ingestDir, ingestScriptDir(input.ingestionProfile)])
|
|
4510
|
-
];
|
|
4511
3728
|
return [
|
|
4512
3729
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
4513
3730
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
4514
|
-
|
|
3731
|
+
"Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
|
|
4515
3732
|
"For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
|
|
4516
3733
|
"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.",
|
|
4517
3734
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
4518
|
-
`Do not modify ${
|
|
3735
|
+
`Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
|
|
4519
3736
|
"Always call reportStatus with status=success once verification has run, even when sufficient=false.",
|
|
4520
3737
|
"Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
|
|
4521
3738
|
"Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
|
|
@@ -4524,17 +3741,14 @@ function verificationInstructions(input) {
|
|
|
4524
3741
|
var IMPLEMENT_CONFIG = {
|
|
4525
3742
|
ingestion: {
|
|
4526
3743
|
title: "Algolia ingestion",
|
|
4527
|
-
label: "Ingestion",
|
|
4528
3744
|
buildInstructions: ingestionInstructions
|
|
4529
3745
|
},
|
|
4530
3746
|
search: {
|
|
4531
3747
|
title: "Algolia search",
|
|
4532
|
-
label: "Search",
|
|
4533
3748
|
buildInstructions: searchInstructions
|
|
4534
3749
|
},
|
|
4535
3750
|
verification: {
|
|
4536
3751
|
title: "Algolia verification",
|
|
4537
|
-
label: "Verification",
|
|
4538
3752
|
buildInstructions: verificationInstructions
|
|
4539
3753
|
}
|
|
4540
3754
|
};
|
|
@@ -4566,10 +3780,11 @@ function buildAgentInstructions(useCase, input, extraInstructions = []) {
|
|
|
4566
3780
|
];
|
|
4567
3781
|
}
|
|
4568
3782
|
function formatSummary(useCase, summary) {
|
|
4569
|
-
|
|
3783
|
+
const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
|
|
3784
|
+
return `${label}: ${summary}`;
|
|
4570
3785
|
}
|
|
4571
|
-
function buildIngestCommand(worktree,
|
|
4572
|
-
return `cd ${shellQuote(worktree)} && ${
|
|
3786
|
+
function buildIngestCommand(worktree, runtime, entrypoint) {
|
|
3787
|
+
return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
|
|
4573
3788
|
}
|
|
4574
3789
|
function parseIngestRecordCount(output) {
|
|
4575
3790
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
@@ -4651,7 +3866,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4651
3866
|
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
4652
3867
|
}
|
|
4653
3868
|
const normalized = normalizeFindingPaths(findings);
|
|
4654
|
-
const
|
|
3869
|
+
const confirmed2 = normalized.confirmedEntities;
|
|
4655
3870
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
4656
3871
|
let appId;
|
|
4657
3872
|
let searchKey;
|
|
@@ -4689,66 +3904,31 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4689
3904
|
);
|
|
4690
3905
|
}
|
|
4691
3906
|
}
|
|
4692
|
-
const ingestionProfile = await resolveIngestionProfile(
|
|
4693
|
-
ctx,
|
|
4694
|
-
language,
|
|
4695
|
-
worktree
|
|
4696
|
-
);
|
|
4697
|
-
const toolchain = await resolveToolchain(worktree, ingestionProfile);
|
|
4698
|
-
const verificationLanguages = [
|
|
4699
|
-
.../* @__PURE__ */ new Set([
|
|
4700
|
-
ingestionProfile.id,
|
|
4701
|
-
...(await detectProfilesFromManifests(worktree)).map((p) => p.id)
|
|
4702
|
-
])
|
|
4703
|
-
];
|
|
4704
|
-
const frameworkName = language.frameworks[0]?.name;
|
|
4705
|
-
const searchStrategy = resolveSearchStrategy(
|
|
4706
|
-
frameworkName,
|
|
4707
|
-
verificationLanguages.includes(JAVASCRIPT)
|
|
4708
|
-
);
|
|
4709
|
-
logger.info(
|
|
4710
|
-
{
|
|
4711
|
-
language: ingestionProfile.id,
|
|
4712
|
-
packageManager: toolchain.packageManager.id,
|
|
4713
|
-
ingest: toolchain.ingest.kind,
|
|
4714
|
-
framework: frameworkName,
|
|
4715
|
-
searchStrategy
|
|
4716
|
-
},
|
|
4717
|
-
"implement: resolved ingestion toolchain and search strategy"
|
|
4718
|
-
);
|
|
4719
3907
|
const input = {
|
|
4720
3908
|
findings: normalized,
|
|
4721
|
-
confirmed:
|
|
3909
|
+
confirmed: confirmed2,
|
|
4722
3910
|
searchLocation,
|
|
4723
3911
|
targetIndex,
|
|
4724
3912
|
language,
|
|
4725
3913
|
appId,
|
|
4726
3914
|
searchKey,
|
|
4727
|
-
searchEnvVars:
|
|
4728
|
-
language,
|
|
4729
|
-
searchStrategy,
|
|
4730
|
-
appId,
|
|
4731
|
-
searchKey
|
|
4732
|
-
),
|
|
3915
|
+
searchEnvVars: searchEnvVars(language, appId, searchKey),
|
|
4733
3916
|
ingestDir: INGEST_DIR,
|
|
4734
3917
|
ingestionSource,
|
|
4735
3918
|
uploadFilePath,
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
toolchain,
|
|
4740
|
-
verificationLanguages
|
|
3919
|
+
// language.frameworks already prefers the confirm-framework step output,
|
|
3920
|
+
// so the user's confirmed stack (not just raw detection) picks the flavor.
|
|
3921
|
+
uiFramework: detectUiFramework(language)
|
|
4741
3922
|
};
|
|
4742
|
-
const searchToolchain = !bundlesJavaScript(searchStrategy) ? void 0 : ingestionProfile.id === JAVASCRIPT ? toolchain : await resolveToolchain(worktree, LANGUAGE_PROFILES[JAVASCRIPT]);
|
|
4743
|
-
const toolchainForUseCase = (useCase) => useCase === "search" ? searchToolchain : toolchain;
|
|
4744
3923
|
const summaries = [];
|
|
4745
3924
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
4746
3925
|
let agentRuns = 0;
|
|
3926
|
+
let ingestRuntime;
|
|
4747
3927
|
let ingestEntrypoint;
|
|
4748
3928
|
let ingestScriptRan = false;
|
|
4749
3929
|
let ingestRecordCount;
|
|
4750
3930
|
let ingestDurationMs;
|
|
4751
|
-
|
|
3931
|
+
let installFailed = false;
|
|
4752
3932
|
let ingestOutcomeMessage;
|
|
4753
3933
|
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
4754
3934
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
@@ -4762,19 +3942,16 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4762
3942
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
4763
3943
|
outputSchema: implementationOutputSchema
|
|
4764
3944
|
});
|
|
4765
|
-
const useCaseToolchain = toolchainForUseCase(currentUseCase);
|
|
4766
|
-
if (!useCaseToolchain) return result;
|
|
4767
3945
|
ctx.notify({
|
|
4768
3946
|
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
4769
3947
|
});
|
|
4770
3948
|
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
4771
|
-
useCase: currentUseCase
|
|
4772
|
-
language: useCaseToolchain.profile.id
|
|
3949
|
+
useCase: currentUseCase
|
|
4773
3950
|
});
|
|
4774
|
-
const install = await installWorktreeDeps(worktree
|
|
3951
|
+
const install = await installWorktreeDeps(worktree);
|
|
4775
3952
|
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
4776
3953
|
if (!install.ok) {
|
|
4777
|
-
|
|
3954
|
+
installFailed = true;
|
|
4778
3955
|
logger.warn(
|
|
4779
3956
|
{ useCase: currentUseCase, output: install.output },
|
|
4780
3957
|
"implement: dependency install in worktree failed; generated commands may not run until deps are installed"
|
|
@@ -4788,16 +3965,15 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4788
3965
|
return runAgent({
|
|
4789
3966
|
instructions: buildAgentInstructions("verification", input),
|
|
4790
3967
|
tools: toolsForUseCase("verification"),
|
|
4791
|
-
outputSchema: verificationOutputSchema
|
|
4792
|
-
// So verifyImplementation runs this repo's checks, not just npm scripts.
|
|
4793
|
-
languages: input.verificationLanguages
|
|
3968
|
+
outputSchema: verificationOutputSchema
|
|
4794
3969
|
});
|
|
4795
3970
|
}
|
|
4796
3971
|
if (useCases.includes("ingestion")) {
|
|
4797
|
-
const { summary, entrypoint } = await runImplementationUseCase("ingestion");
|
|
3972
|
+
const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
|
|
4798
3973
|
summaries.push(formatSummary("ingestion", summary));
|
|
3974
|
+
ingestRuntime = runtime;
|
|
4799
3975
|
ingestEntrypoint = entrypoint;
|
|
4800
|
-
if (
|
|
3976
|
+
if (ingestRuntime && ingestEntrypoint && !installFailed) {
|
|
4801
3977
|
ctx.clearNotices();
|
|
4802
3978
|
const runNow = await ctx.requestUserInput({
|
|
4803
3979
|
prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
|
|
@@ -4809,13 +3985,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4809
3985
|
const profile = await loadActiveProfile();
|
|
4810
3986
|
ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
|
|
4811
3987
|
const scriptLogId = ctx.logStart("runIngestScript", {
|
|
4812
|
-
|
|
3988
|
+
runtime: ingestRuntime,
|
|
4813
3989
|
entrypoint: ingestEntrypoint
|
|
4814
3990
|
});
|
|
4815
3991
|
const startedAt = Date.now();
|
|
4816
3992
|
const run2 = await runIngestScript(
|
|
4817
3993
|
worktree,
|
|
4818
|
-
|
|
3994
|
+
ingestRuntime,
|
|
4819
3995
|
ingestEntrypoint,
|
|
4820
3996
|
{
|
|
4821
3997
|
[APP_ID_VAR]: profile.appId,
|
|
@@ -4829,7 +4005,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4829
4005
|
ingestRecordCount = parseIngestRecordCount(run2.output);
|
|
4830
4006
|
if (ingestRecordCount != null) {
|
|
4831
4007
|
track("AI Wizard Ingest Successful", {
|
|
4832
|
-
entity_name:
|
|
4008
|
+
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
4833
4009
|
record_count: ingestRecordCount,
|
|
4834
4010
|
duration_ms: ingestDurationMs
|
|
4835
4011
|
});
|
|
@@ -4842,7 +4018,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4842
4018
|
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
|
|
4843
4019
|
logger.warn(
|
|
4844
4020
|
{
|
|
4845
|
-
|
|
4021
|
+
runtime: ingestRuntime,
|
|
4846
4022
|
entrypoint: ingestEntrypoint,
|
|
4847
4023
|
reason: run2.reason
|
|
4848
4024
|
},
|
|
@@ -4865,7 +4041,7 @@ ${run2.output}` : status;
|
|
|
4865
4041
|
outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
|
|
4866
4042
|
logger.warn(
|
|
4867
4043
|
{
|
|
4868
|
-
|
|
4044
|
+
runtime: ingestRuntime,
|
|
4869
4045
|
entrypoint: ingestEntrypoint,
|
|
4870
4046
|
output: run2.output
|
|
4871
4047
|
},
|
|
@@ -4882,28 +4058,10 @@ ${run2.output}` : status;
|
|
|
4882
4058
|
}
|
|
4883
4059
|
}
|
|
4884
4060
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
4885
|
-
if (ingestEntrypoint) {
|
|
4061
|
+
if (ingestRuntime && ingestEntrypoint) {
|
|
4886
4062
|
commandMessages.push(
|
|
4887
|
-
`Ingestion command: ${buildIngestCommand(worktree,
|
|
4063
|
+
`Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
|
|
4888
4064
|
);
|
|
4889
|
-
if (toolchain.ingest.kind === "manual") {
|
|
4890
|
-
commandMessages.push(
|
|
4891
|
-
`The wizard does not run ${ingestionProfile.displayName} ${toolchain.packageManager.id} projects \u2014 run the command above yourself to ingest.`
|
|
4892
|
-
);
|
|
4893
|
-
const missingTask = await missingBuildTask(worktree, toolchain);
|
|
4894
|
-
if (missingTask) {
|
|
4895
|
-
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.`;
|
|
4896
|
-
commandMessages.push(warning);
|
|
4897
|
-
summaries.push(warning);
|
|
4898
|
-
}
|
|
4899
|
-
}
|
|
4900
|
-
}
|
|
4901
|
-
if (ingestionSource === "local") {
|
|
4902
|
-
const limitation = localSourceLimitation(worktree, ingestionProfile);
|
|
4903
|
-
if (limitation) {
|
|
4904
|
-
commandMessages.push(`\u26A0\uFE0F ${limitation}`);
|
|
4905
|
-
summaries.push(`\u26A0\uFE0F ${limitation}`);
|
|
4906
|
-
}
|
|
4907
4065
|
}
|
|
4908
4066
|
await ctx.requestUserInput({
|
|
4909
4067
|
// No question being asked here, just an acknowledgement — the
|
|
@@ -4914,20 +4072,7 @@ ${run2.output}` : status;
|
|
|
4914
4072
|
messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
|
|
4915
4073
|
});
|
|
4916
4074
|
}
|
|
4917
|
-
|
|
4918
|
-
if (skipSearch) {
|
|
4919
|
-
const target = describeSearchTarget(
|
|
4920
|
-
input.searchStrategy,
|
|
4921
|
-
input.frameworkName
|
|
4922
|
-
);
|
|
4923
|
-
summaries.push(
|
|
4924
|
-
`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).`
|
|
4925
|
-
);
|
|
4926
|
-
track("AI Wizard Search UI Skipped", {
|
|
4927
|
-
framework: input.frameworkName ?? "unknown"
|
|
4928
|
-
});
|
|
4929
|
-
}
|
|
4930
|
-
if (useCases.includes("search") && !skipSearch) {
|
|
4075
|
+
if (useCases.includes("search")) {
|
|
4931
4076
|
let extraInstructions = [];
|
|
4932
4077
|
const preSearchFiles = new Set(await listChangedFiles(worktree));
|
|
4933
4078
|
for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
|
|
@@ -4998,9 +4143,9 @@ ${run2.output}` : status;
|
|
|
4998
4143
|
"implement: agent reported success but no files changed in the worktree"
|
|
4999
4144
|
);
|
|
5000
4145
|
}
|
|
5001
|
-
if (
|
|
4146
|
+
if (installFailed) {
|
|
5002
4147
|
summaries.push(
|
|
5003
|
-
|
|
4148
|
+
'\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".'
|
|
5004
4149
|
);
|
|
5005
4150
|
}
|
|
5006
4151
|
return {
|
|
@@ -5008,10 +4153,10 @@ ${run2.output}` : status;
|
|
|
5008
4153
|
filesChanged,
|
|
5009
4154
|
summary: summaries.join("\n\n"),
|
|
5010
4155
|
worktreePath: worktree,
|
|
5011
|
-
...useCases.includes("ingestion") && ingestEntrypoint ? {
|
|
4156
|
+
...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
|
|
5012
4157
|
ingestCommand: buildIngestCommand(
|
|
5013
4158
|
worktree,
|
|
5014
|
-
|
|
4159
|
+
ingestRuntime,
|
|
5015
4160
|
ingestEntrypoint
|
|
5016
4161
|
),
|
|
5017
4162
|
ingestScriptRan,
|
|
@@ -5340,20 +4485,20 @@ function parseCliArgs(argv) {
|
|
|
5340
4485
|
}
|
|
5341
4486
|
|
|
5342
4487
|
// src/lib/resetState.ts
|
|
5343
|
-
import { readdir as
|
|
5344
|
-
import { join as
|
|
4488
|
+
import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
4489
|
+
import { join as join12 } from "node:path";
|
|
5345
4490
|
var KEEP = ["wizard.log"];
|
|
5346
4491
|
async function resetProjectState() {
|
|
5347
4492
|
const dir = stateDir();
|
|
5348
4493
|
let entries;
|
|
5349
4494
|
try {
|
|
5350
|
-
entries = await
|
|
4495
|
+
entries = await readdir4(dir);
|
|
5351
4496
|
} catch {
|
|
5352
4497
|
return { dir, removed: [] };
|
|
5353
4498
|
}
|
|
5354
4499
|
const targets = entries.filter((name) => !KEEP.includes(name));
|
|
5355
4500
|
await Promise.all(
|
|
5356
|
-
targets.map((name) => rm2(
|
|
4501
|
+
targets.map((name) => rm2(join12(dir, name), { recursive: true, force: true }))
|
|
5357
4502
|
);
|
|
5358
4503
|
return { dir, removed: targets };
|
|
5359
4504
|
}
|