@algolia/wizard 0.6.0-rc.55.29 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +337 -1251
- package/docs/algolia-sdk/README.md +30 -53
- package/docs/algolia-sdk/search-single-index.md +42 -0
- package/package.json +1 -1
- 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-dart.md +0 -74
- 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/docs/algolia-sdk/save-records-swift.md +0 -88
package/dist/main.js
CHANGED
|
@@ -796,12 +796,12 @@ var sidebarItems = [
|
|
|
796
796
|
description: "push 100 records to Algolia in seconds"
|
|
797
797
|
},
|
|
798
798
|
{
|
|
799
|
-
title: "detect your
|
|
800
|
-
description: "React, Vue, Angular,
|
|
799
|
+
title: "detect your framework",
|
|
800
|
+
description: "React, Vue, Angular, Vanilla JS"
|
|
801
801
|
},
|
|
802
802
|
{
|
|
803
803
|
title: "scaffold a search UI",
|
|
804
|
-
description: "a styled InstantSearch
|
|
804
|
+
description: "a styled InstantSearch component, wired into your app"
|
|
805
805
|
},
|
|
806
806
|
{
|
|
807
807
|
title: "ship it",
|
|
@@ -907,7 +907,7 @@ var accessItems = [
|
|
|
907
907
|
{
|
|
908
908
|
tag: "READ",
|
|
909
909
|
title: "Project files",
|
|
910
|
-
description: "reads
|
|
910
|
+
description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
911
911
|
},
|
|
912
912
|
{
|
|
913
913
|
tag: "WRITE",
|
|
@@ -2155,719 +2155,15 @@ function writeCredentialsTool(ctx) {
|
|
|
2155
2155
|
// src/lib/tools/searchFiles.ts
|
|
2156
2156
|
import { tool as tool7 } from "ai";
|
|
2157
2157
|
import z10 from "zod";
|
|
2158
|
-
import { readdir as
|
|
2159
|
-
import { join as join10 } from "node:path";
|
|
2160
|
-
|
|
2161
|
-
// src/lib/languages.ts
|
|
2162
|
-
import { readdir as readdir2 } from "node:fs/promises";
|
|
2163
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
2164
|
-
import { join as join9 } from "node:path";
|
|
2165
|
-
|
|
2166
|
-
// src/lib/tools/utils/packageManager.ts
|
|
2167
|
-
import { readFile as readFile6 } from "node:fs/promises";
|
|
2168
|
-
import { existsSync } from "node:fs";
|
|
2158
|
+
import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
|
|
2169
2159
|
import { join as join8 } from "node:path";
|
|
2170
|
-
var LOCKFILES = [
|
|
2171
|
-
["pnpm-lock.yaml", "pnpm"],
|
|
2172
|
-
["yarn.lock", "yarn"],
|
|
2173
|
-
["bun.lockb", "bun"],
|
|
2174
|
-
["bun.lock", "bun"],
|
|
2175
|
-
["package-lock.json", "npm"]
|
|
2176
|
-
];
|
|
2177
|
-
async function readPackageJson(cwd = process.cwd()) {
|
|
2178
|
-
return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
|
|
2179
|
-
}
|
|
2180
|
-
function packageManagerFrom(pkg) {
|
|
2181
|
-
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2182
|
-
}
|
|
2183
|
-
function packageManagerFromLockfile(cwd) {
|
|
2184
|
-
return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
|
|
2185
|
-
}
|
|
2186
|
-
async function detectPackageManager(cwd) {
|
|
2187
|
-
try {
|
|
2188
|
-
const pkg = await readPackageJson(cwd);
|
|
2189
|
-
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2190
|
-
} catch {
|
|
2191
|
-
}
|
|
2192
|
-
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2193
|
-
}
|
|
2194
|
-
|
|
2195
|
-
// src/lib/shell.ts
|
|
2196
|
-
function shellQuote(value) {
|
|
2197
|
-
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
|
-
// src/lib/languages.ts
|
|
2201
|
-
var ENTRYPOINT_TOKEN = "{entrypoint}";
|
|
2202
|
-
var INGEST_DIR = ".algolia-wizard";
|
|
2203
|
-
var VISIBLE_INGEST_DIR = "algolia-wizard";
|
|
2204
|
-
var PY_VENV = `${INGEST_DIR}/.venv`;
|
|
2205
|
-
var PY_VENV_PYTHON = `${PY_VENV}/bin/python`;
|
|
2206
|
-
var PY_REQUIREMENTS = `${INGEST_DIR}/requirements.txt`;
|
|
2207
|
-
var CSHARP_PROJECT = `${INGEST_DIR}/ingest/ingest.csproj`;
|
|
2208
|
-
var SWIFT_PACKAGE_DIR = `${INGEST_DIR}/Ingest`;
|
|
2209
|
-
var LANGUAGE_PROFILES = {
|
|
2210
|
-
javascript: {
|
|
2211
|
-
id: "javascript",
|
|
2212
|
-
displayName: "JavaScript/TypeScript",
|
|
2213
|
-
aliases: [
|
|
2214
|
-
"javascript",
|
|
2215
|
-
"js",
|
|
2216
|
-
"typescript",
|
|
2217
|
-
"ts",
|
|
2218
|
-
"node",
|
|
2219
|
-
"nodejs",
|
|
2220
|
-
"node.js",
|
|
2221
|
-
"bun",
|
|
2222
|
-
"deno",
|
|
2223
|
-
"ecmascript",
|
|
2224
|
-
"jsx",
|
|
2225
|
-
"tsx"
|
|
2226
|
-
],
|
|
2227
|
-
manifests: ["package.json"],
|
|
2228
|
-
// The concrete npm-family manager is resolved by detectPackageManager (it
|
|
2229
|
-
// honours the package.json `packageManager` field, which lockfiles can't
|
|
2230
|
-
// express), so one spec covers all four and `resolveToolchain` rewrites the
|
|
2231
|
-
// binary below.
|
|
2232
|
-
packageManagers: [
|
|
2233
|
-
{
|
|
2234
|
-
id: "npm",
|
|
2235
|
-
dependency: { mode: "agent-declares", file: "package.json" },
|
|
2236
|
-
installSteps: [{ argv: ["npm", "install"] }],
|
|
2237
|
-
ingest: {
|
|
2238
|
-
kind: "auto",
|
|
2239
|
-
argv: ["node", ENTRYPOINT_TOKEN],
|
|
2240
|
-
entrypointExtensions: [".mjs", ".cjs", ".js"]
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
2243
|
-
],
|
|
2244
|
-
sdk: { packageName: "algoliasearch", versionPin: "^5", docKey: "js" },
|
|
2245
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.mjs`,
|
|
2246
|
-
// package.json scripts are repo-defined, so they're resolved at run time by
|
|
2247
|
-
// repoVerification rather than listed here.
|
|
2248
|
-
verification: [],
|
|
2249
|
-
envReadInstruction: "Read them from `process.env`.",
|
|
2250
|
-
skipDirs: ["node_modules", "dist", "build", "coverage", ".next", "out"]
|
|
2251
|
-
},
|
|
2252
|
-
python: {
|
|
2253
|
-
id: "python",
|
|
2254
|
-
displayName: "Python",
|
|
2255
|
-
aliases: ["python", "python3", "py", "cpython"],
|
|
2256
|
-
manifests: [
|
|
2257
|
-
"pyproject.toml",
|
|
2258
|
-
"requirements.txt",
|
|
2259
|
-
"setup.py",
|
|
2260
|
-
"setup.cfg",
|
|
2261
|
-
"Pipfile"
|
|
2262
|
-
],
|
|
2263
|
-
// Deliberately one path for every Python repo: a wizard-owned venv under
|
|
2264
|
-
// .algolia-wizard. Reusing the project's uv/poetry environment would mean
|
|
2265
|
-
// mutating the developer's real dependency manifest and lockfile, and the
|
|
2266
|
-
// declare-here/install-there split is the main way ingestion silently ends
|
|
2267
|
-
// up without the SDK installed. The tradeoff: the script can import the
|
|
2268
|
-
// Algolia client and anything it declares itself, but not the project's own
|
|
2269
|
-
// packages (see the optional root-requirements step below).
|
|
2270
|
-
packageManagers: [
|
|
2271
|
-
{
|
|
2272
|
-
id: "pip-venv",
|
|
2273
|
-
dependency: { mode: "agent-declares", file: PY_REQUIREMENTS },
|
|
2274
|
-
installSteps: [
|
|
2275
|
-
{ argv: ["python3", "-m", "venv", PY_VENV] },
|
|
2276
|
-
{
|
|
2277
|
-
argv: [PY_VENV_PYTHON, "-m", "pip", "install", "-r", PY_REQUIREMENTS]
|
|
2278
|
-
},
|
|
2279
|
-
// Best-effort access to the project's own dependencies (DB drivers,
|
|
2280
|
-
// ORMs) when the repo pins them the classic way.
|
|
2281
|
-
{
|
|
2282
|
-
argv: [
|
|
2283
|
-
PY_VENV_PYTHON,
|
|
2284
|
-
"-m",
|
|
2285
|
-
"pip",
|
|
2286
|
-
"install",
|
|
2287
|
-
"-r",
|
|
2288
|
-
"requirements.txt"
|
|
2289
|
-
],
|
|
2290
|
-
requiresFile: "requirements.txt",
|
|
2291
|
-
optional: true
|
|
2292
|
-
}
|
|
2293
|
-
],
|
|
2294
|
-
ingest: {
|
|
2295
|
-
kind: "auto",
|
|
2296
|
-
argv: [PY_VENV_PYTHON, ENTRYPOINT_TOKEN],
|
|
2297
|
-
entrypointExtensions: [".py"]
|
|
2298
|
-
}
|
|
2299
|
-
}
|
|
2300
|
-
],
|
|
2301
|
-
sdk: {
|
|
2302
|
-
packageName: "algoliasearch",
|
|
2303
|
-
versionPin: ">=4,<5",
|
|
2304
|
-
docKey: "python"
|
|
2305
|
-
},
|
|
2306
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.py`,
|
|
2307
|
-
verification: [
|
|
2308
|
-
{
|
|
2309
|
-
// -x skips the venv this same directory holds; without it the check
|
|
2310
|
-
// compiles every installed package instead of the generated script.
|
|
2311
|
-
label: "python compileall",
|
|
2312
|
-
argv: ["python3", "-m", "compileall", "-q", "-x", "[.]venv", INGEST_DIR],
|
|
2313
|
-
requiresFile: INGEST_DIR
|
|
2314
|
-
}
|
|
2315
|
-
],
|
|
2316
|
-
envReadInstruction: "Read them from `os.environ`.",
|
|
2317
|
-
skipDirs: [
|
|
2318
|
-
"venv",
|
|
2319
|
-
"__pycache__",
|
|
2320
|
-
"site-packages",
|
|
2321
|
-
"dist",
|
|
2322
|
-
"build",
|
|
2323
|
-
"htmlcov"
|
|
2324
|
-
]
|
|
2325
|
-
},
|
|
2326
|
-
ruby: {
|
|
2327
|
-
id: "ruby",
|
|
2328
|
-
displayName: "Ruby",
|
|
2329
|
-
aliases: ["ruby", "rb", "rails", "ruby on rails", "rubyonrails"],
|
|
2330
|
-
manifests: ["Gemfile", "*.gemspec"],
|
|
2331
|
-
packageManagers: [
|
|
2332
|
-
{
|
|
2333
|
-
id: "bundler",
|
|
2334
|
-
dependency: { mode: "agent-declares", file: "Gemfile" },
|
|
2335
|
-
installSteps: [{ argv: ["bundle", "install"] }],
|
|
2336
|
-
ingest: {
|
|
2337
|
-
kind: "auto",
|
|
2338
|
-
argv: ["bundle", "exec", "ruby", ENTRYPOINT_TOKEN],
|
|
2339
|
-
entrypointExtensions: [".rb"]
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
],
|
|
2343
|
-
sdk: { packageName: "algolia", versionPin: "~> 3.0", docKey: "ruby" },
|
|
2344
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.rb`,
|
|
2345
|
-
// Ruby has no directory-level syntax check (`ruby -c` is one file at a
|
|
2346
|
-
// time), so verification relies on the agent's own review here.
|
|
2347
|
-
verification: [],
|
|
2348
|
-
envReadInstruction: "Read them from `ENV.fetch('NAME')`.",
|
|
2349
|
-
skipDirs: ["vendor", "tmp", "log", "coverage"]
|
|
2350
|
-
},
|
|
2351
|
-
php: {
|
|
2352
|
-
id: "php",
|
|
2353
|
-
displayName: "PHP",
|
|
2354
|
-
aliases: ["php", "laravel", "symfony"],
|
|
2355
|
-
manifests: ["composer.json"],
|
|
2356
|
-
packageManagers: [
|
|
2357
|
-
{
|
|
2358
|
-
id: "composer",
|
|
2359
|
-
// `composer require` both declares and installs, and unlike editing
|
|
2360
|
-
// composer.json by hand it can't leave composer.lock out of date (which
|
|
2361
|
-
// makes a later `composer install` refuse to run).
|
|
2362
|
-
dependency: { mode: "wizard-installs" },
|
|
2363
|
-
installSteps: [
|
|
2364
|
-
{
|
|
2365
|
-
argv: [
|
|
2366
|
-
"composer",
|
|
2367
|
-
"require",
|
|
2368
|
-
"algolia/algoliasearch-client-php:^4",
|
|
2369
|
-
"--no-interaction",
|
|
2370
|
-
// Repo post-install scripts are the project's code, not ours to
|
|
2371
|
-
// trigger; Laravel's package:discover also fails in a bare tree.
|
|
2372
|
-
"--no-scripts"
|
|
2373
|
-
]
|
|
2374
|
-
}
|
|
2375
|
-
],
|
|
2376
|
-
ingest: {
|
|
2377
|
-
kind: "auto",
|
|
2378
|
-
argv: ["php", ENTRYPOINT_TOKEN],
|
|
2379
|
-
entrypointExtensions: [".php"]
|
|
2380
|
-
}
|
|
2381
|
-
}
|
|
2382
|
-
],
|
|
2383
|
-
sdk: {
|
|
2384
|
-
packageName: "algolia/algoliasearch-client-php",
|
|
2385
|
-
versionPin: "^4",
|
|
2386
|
-
docKey: "php"
|
|
2387
|
-
},
|
|
2388
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.php`,
|
|
2389
|
-
verification: [],
|
|
2390
|
-
envReadInstruction: "Read them from `getenv('NAME')`.",
|
|
2391
|
-
skipDirs: ["vendor", "node_modules"]
|
|
2392
|
-
},
|
|
2393
|
-
go: {
|
|
2394
|
-
id: "go",
|
|
2395
|
-
displayName: "Go",
|
|
2396
|
-
aliases: ["go", "golang"],
|
|
2397
|
-
manifests: ["go.mod"],
|
|
2398
|
-
packageManagers: [
|
|
2399
|
-
{
|
|
2400
|
-
id: "gomod",
|
|
2401
|
-
// Imports in the generated file are the declaration; `go mod tidy`
|
|
2402
|
-
// resolves and fetches them — which only works because the script lives
|
|
2403
|
-
// outside INGEST_DIR (see VISIBLE_INGEST_DIR).
|
|
2404
|
-
dependency: { mode: "code-imports" },
|
|
2405
|
-
installSteps: [{ argv: ["go", "mod", "tidy"] }],
|
|
2406
|
-
ingest: {
|
|
2407
|
-
kind: "auto",
|
|
2408
|
-
argv: ["go", "run", ENTRYPOINT_TOKEN],
|
|
2409
|
-
entrypointExtensions: [".go"]
|
|
2410
|
-
}
|
|
2411
|
-
}
|
|
2412
|
-
],
|
|
2413
|
-
sdk: {
|
|
2414
|
-
packageName: "github.com/algolia/algoliasearch-client-go/v4",
|
|
2415
|
-
versionPin: "v4",
|
|
2416
|
-
docKey: "go"
|
|
2417
|
-
},
|
|
2418
|
-
ingestEntrypointExample: `${VISIBLE_INGEST_DIR}/ingest.go`,
|
|
2419
|
-
verification: [
|
|
2420
|
-
{ label: "go vet", argv: ["go", "vet", "./..."], requiresFile: "go.mod" }
|
|
2421
|
-
],
|
|
2422
|
-
envReadInstruction: "Read them from `os.Getenv`.",
|
|
2423
|
-
skipDirs: ["vendor", "bin"]
|
|
2424
|
-
},
|
|
2425
|
-
java: {
|
|
2426
|
-
id: "java",
|
|
2427
|
-
displayName: "Java",
|
|
2428
|
-
aliases: ["java"],
|
|
2429
|
-
manifests: ["pom.xml", "build.gradle", "build.gradle.kts"],
|
|
2430
|
-
packageManagers: [
|
|
2431
|
-
{
|
|
2432
|
-
id: "maven",
|
|
2433
|
-
detectFiles: ["pom.xml"],
|
|
2434
|
-
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2435
|
-
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2436
|
-
// The main class is a wizard constant the instructions require the agent
|
|
2437
|
-
// to use, so execution can't be redirected by agent output. Runnable only
|
|
2438
|
-
// because the install step above compiles src/main/java first — which is
|
|
2439
|
-
// why the entrypoint lives there rather than under .algolia-wizard/.
|
|
2440
|
-
ingest: {
|
|
2441
|
-
kind: "auto",
|
|
2442
|
-
argv: [
|
|
2443
|
-
"mvn",
|
|
2444
|
-
"-q",
|
|
2445
|
-
"org.codehaus.mojo:exec-maven-plugin:3.5.0:java",
|
|
2446
|
-
"-Dexec.mainClass=AlgoliaWizardIngest"
|
|
2447
|
-
],
|
|
2448
|
-
entrypointExtensions: [".java"]
|
|
2449
|
-
}
|
|
2450
|
-
},
|
|
2451
|
-
{
|
|
2452
|
-
id: "gradle",
|
|
2453
|
-
detectFiles: ["build.gradle", "build.gradle.kts"],
|
|
2454
|
-
dependency: {
|
|
2455
|
-
mode: "agent-declares",
|
|
2456
|
-
file: "build.gradle",
|
|
2457
|
-
alternatives: ["build.gradle.kts"]
|
|
2458
|
-
},
|
|
2459
|
-
installSteps: [],
|
|
2460
|
-
// Auto-running means executing the repo's own ./gradlew wrapper; out of
|
|
2461
|
-
// scope for now, so the wizard writes the code and prints the command.
|
|
2462
|
-
ingest: {
|
|
2463
|
-
kind: "manual",
|
|
2464
|
-
entrypointExtensions: [".java"],
|
|
2465
|
-
runCommand: "./gradlew runAlgoliaIngest"
|
|
2466
|
-
}
|
|
2467
|
-
}
|
|
2468
|
-
],
|
|
2469
|
-
sdk: {
|
|
2470
|
-
packageName: "com.algolia:algoliasearch",
|
|
2471
|
-
versionPin: "4.+",
|
|
2472
|
-
docKey: "java",
|
|
2473
|
-
alsoRequires: "The class must be named AlgoliaWizardIngest, in the default package (no `package` statement), with a `public static void main`."
|
|
2474
|
-
},
|
|
2475
|
-
// Not under .algolia-wizard/: Maven and Gradle only compile src/main/<lang>,
|
|
2476
|
-
// so a class outside it never makes it onto the classpath and the run command
|
|
2477
|
-
// fails with "class not found".
|
|
2478
|
-
ingestEntrypointExample: "src/main/java/AlgoliaWizardIngest.java",
|
|
2479
|
-
verification: [
|
|
2480
|
-
{
|
|
2481
|
-
label: "mvn compile",
|
|
2482
|
-
argv: ["mvn", "-q", "-DskipTests", "compile"],
|
|
2483
|
-
requiresFile: "pom.xml"
|
|
2484
|
-
}
|
|
2485
|
-
],
|
|
2486
|
-
envReadInstruction: "Read them from `System.getenv`.",
|
|
2487
|
-
skipDirs: ["target", "build", "out"]
|
|
2488
|
-
},
|
|
2489
|
-
kotlin: {
|
|
2490
|
-
id: "kotlin",
|
|
2491
|
-
displayName: "Kotlin",
|
|
2492
|
-
aliases: ["kotlin", "kt", "ktor"],
|
|
2493
|
-
manifests: ["build.gradle.kts", "build.gradle", "pom.xml"],
|
|
2494
|
-
packageManagers: [
|
|
2495
|
-
{
|
|
2496
|
-
id: "gradle",
|
|
2497
|
-
detectFiles: ["build.gradle.kts", "build.gradle"],
|
|
2498
|
-
dependency: {
|
|
2499
|
-
mode: "agent-declares",
|
|
2500
|
-
file: "build.gradle.kts",
|
|
2501
|
-
alternatives: ["build.gradle"]
|
|
2502
|
-
},
|
|
2503
|
-
installSteps: [],
|
|
2504
|
-
ingest: {
|
|
2505
|
-
kind: "manual",
|
|
2506
|
-
entrypointExtensions: [".kt"],
|
|
2507
|
-
runCommand: "./gradlew runAlgoliaIngest"
|
|
2508
|
-
}
|
|
2509
|
-
},
|
|
2510
|
-
// Kotlin/Maven is rare but real, and pom.xml is a Kotlin manifest — without
|
|
2511
|
-
// this spec such a repo falls through to Gradle and is told to run a
|
|
2512
|
-
// ./gradlew task that doesn't exist. Compiling needs the repo's own
|
|
2513
|
-
// kotlin-maven-plugin, so the run stays the developer's step.
|
|
2514
|
-
{
|
|
2515
|
-
id: "maven",
|
|
2516
|
-
detectFiles: ["pom.xml"],
|
|
2517
|
-
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2518
|
-
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2519
|
-
ingest: {
|
|
2520
|
-
kind: "manual",
|
|
2521
|
-
entrypointExtensions: [".kt"],
|
|
2522
|
-
runCommand: "mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java -Dexec.mainClass=AlgoliaWizardIngest"
|
|
2523
|
-
}
|
|
2524
|
-
}
|
|
2525
|
-
],
|
|
2526
|
-
sdk: {
|
|
2527
|
-
packageName: "com.algolia:algoliasearch-client-kotlin",
|
|
2528
|
-
versionPin: "3.+",
|
|
2529
|
-
docKey: "kotlin",
|
|
2530
|
-
// The published client's commonMain ships only ktor-client-core; without an
|
|
2531
|
-
// engine the script compiles and then fails at its first request.
|
|
2532
|
-
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."
|
|
2533
|
-
},
|
|
2534
|
-
ingestEntrypointExample: "src/main/kotlin/AlgoliaWizardIngest.kt",
|
|
2535
|
-
verification: [],
|
|
2536
|
-
envReadInstruction: "Read them from `System.getenv`.",
|
|
2537
|
-
skipDirs: ["build", "out"]
|
|
2538
|
-
},
|
|
2539
|
-
scala: {
|
|
2540
|
-
id: "scala",
|
|
2541
|
-
displayName: "Scala",
|
|
2542
|
-
aliases: ["scala", "sbt"],
|
|
2543
|
-
manifests: ["build.sbt", "build.sc"],
|
|
2544
|
-
packageManagers: [
|
|
2545
|
-
{
|
|
2546
|
-
id: "sbt",
|
|
2547
|
-
dependency: { mode: "agent-declares", file: "build.sbt" },
|
|
2548
|
-
installSteps: [],
|
|
2549
|
-
ingest: {
|
|
2550
|
-
kind: "manual",
|
|
2551
|
-
entrypointExtensions: [".scala"],
|
|
2552
|
-
runCommand: 'sbt "runMain AlgoliaWizardIngest"'
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
|
-
],
|
|
2556
|
-
sdk: {
|
|
2557
|
-
packageName: "com.algolia:algoliasearch-scala_2.13",
|
|
2558
|
-
versionPin: "2.+",
|
|
2559
|
-
docKey: "scala",
|
|
2560
|
-
alsoRequires: "Name the object AlgoliaWizardIngest in the default package (no `package` statement) so `runMain AlgoliaWizardIngest` resolves it."
|
|
2561
|
-
},
|
|
2562
|
-
ingestEntrypointExample: "src/main/scala/AlgoliaWizardIngest.scala",
|
|
2563
|
-
verification: [],
|
|
2564
|
-
envReadInstruction: "Read them from `sys.env`.",
|
|
2565
|
-
// `project/` holds sbt's build definition, but the name is generic enough
|
|
2566
|
-
// that some repos use it for source; scanning it is cheap, missing source
|
|
2567
|
-
// is not.
|
|
2568
|
-
skipDirs: ["target"]
|
|
2569
|
-
},
|
|
2570
|
-
csharp: {
|
|
2571
|
-
id: "csharp",
|
|
2572
|
-
displayName: "C#",
|
|
2573
|
-
aliases: ["c#", "csharp", "cs", ".net", "dotnet", "net", "asp.net"],
|
|
2574
|
-
manifests: ["*.csproj", "*.sln", "global.json"],
|
|
2575
|
-
packageManagers: [
|
|
2576
|
-
{
|
|
2577
|
-
id: "dotnet",
|
|
2578
|
-
// A self-contained project under .algolia-wizard keeps the ingest script
|
|
2579
|
-
// out of the repo's own build graph.
|
|
2580
|
-
dependency: { mode: "agent-declares", file: CSHARP_PROJECT },
|
|
2581
|
-
installSteps: [{ argv: ["dotnet", "restore", CSHARP_PROJECT] }],
|
|
2582
|
-
ingest: {
|
|
2583
|
-
kind: "auto",
|
|
2584
|
-
argv: ["dotnet", "run", "--project", ENTRYPOINT_TOKEN],
|
|
2585
|
-
entrypointExtensions: [".csproj"]
|
|
2586
|
-
}
|
|
2587
|
-
}
|
|
2588
|
-
],
|
|
2589
|
-
sdk: {
|
|
2590
|
-
packageName: "Algolia.Search",
|
|
2591
|
-
versionPin: "7.*",
|
|
2592
|
-
docKey: "csharp"
|
|
2593
|
-
},
|
|
2594
|
-
ingestEntrypointExample: CSHARP_PROJECT,
|
|
2595
|
-
verification: [
|
|
2596
|
-
{
|
|
2597
|
-
label: "dotnet build",
|
|
2598
|
-
argv: ["dotnet", "build", CSHARP_PROJECT, "--nologo"],
|
|
2599
|
-
requiresFile: CSHARP_PROJECT
|
|
2600
|
-
}
|
|
2601
|
-
],
|
|
2602
|
-
envReadInstruction: 'Read them from `Environment.GetEnvironmentVariable("NAME")`.',
|
|
2603
|
-
// Deliberately not `packages`: modern .NET uses PackageReference, and
|
|
2604
|
-
// `packages/` is where pnpm/Lerna/Turborepo monorepos keep all their source —
|
|
2605
|
-
// skipping it would hide the entities the scan is looking for.
|
|
2606
|
-
skipDirs: ["bin", "obj"]
|
|
2607
|
-
},
|
|
2608
|
-
swift: {
|
|
2609
|
-
id: "swift",
|
|
2610
|
-
displayName: "Swift",
|
|
2611
|
-
aliases: ["swift", "swiftui", "ios", "vapor"],
|
|
2612
|
-
manifests: ["Package.swift", "*.xcodeproj", "*.xcworkspace"],
|
|
2613
|
-
packageManagers: [
|
|
2614
|
-
{
|
|
2615
|
-
id: "swiftpm",
|
|
2616
|
-
dependency: {
|
|
2617
|
-
mode: "agent-declares",
|
|
2618
|
-
file: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2619
|
-
},
|
|
2620
|
-
// `swift build` resolves and fetches; a cold build of the client is slow
|
|
2621
|
-
// (minutes), which is why the caller degrades to the manual command when
|
|
2622
|
-
// this fails.
|
|
2623
|
-
installSteps: [
|
|
2624
|
-
{
|
|
2625
|
-
argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2626
|
-
requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2627
|
-
}
|
|
2628
|
-
],
|
|
2629
|
-
ingest: {
|
|
2630
|
-
kind: "auto",
|
|
2631
|
-
argv: ["swift", "run", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2632
|
-
entrypointExtensions: [".swift"]
|
|
2633
|
-
}
|
|
2634
|
-
}
|
|
2635
|
-
],
|
|
2636
|
-
sdk: {
|
|
2637
|
-
packageName: "algoliasearch-client-swift",
|
|
2638
|
-
// SwiftPM range syntax, not an exact version — a bare "9.0.0" in a
|
|
2639
|
-
// Package.swift dependency pins the patch.
|
|
2640
|
-
versionPin: 'from: "9.0.0"',
|
|
2641
|
-
docKey: "swift"
|
|
2642
|
-
},
|
|
2643
|
-
ingestEntrypointExample: `${SWIFT_PACKAGE_DIR}/Sources/Ingest/main.swift`,
|
|
2644
|
-
verification: [
|
|
2645
|
-
{
|
|
2646
|
-
label: "swift build",
|
|
2647
|
-
argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2648
|
-
requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2649
|
-
}
|
|
2650
|
-
],
|
|
2651
|
-
envReadInstruction: "Read them from `ProcessInfo.processInfo.environment`.",
|
|
2652
|
-
skipDirs: ["Pods", "DerivedData", "Carthage", ".build"]
|
|
2653
|
-
},
|
|
2654
|
-
dart: {
|
|
2655
|
-
id: "dart",
|
|
2656
|
-
displayName: "Dart",
|
|
2657
|
-
aliases: ["dart", "flutter"],
|
|
2658
|
-
manifests: ["pubspec.yaml"],
|
|
2659
|
-
packageManagers: [
|
|
2660
|
-
{
|
|
2661
|
-
id: "flutter-pub",
|
|
2662
|
-
detectFiles: [".metadata"],
|
|
2663
|
-
dependency: { mode: "agent-declares", file: "pubspec.yaml" },
|
|
2664
|
-
installSteps: [{ argv: ["flutter", "pub", "get"] }],
|
|
2665
|
-
ingest: {
|
|
2666
|
-
kind: "auto",
|
|
2667
|
-
argv: ["dart", "run", ENTRYPOINT_TOKEN],
|
|
2668
|
-
entrypointExtensions: [".dart"]
|
|
2669
|
-
}
|
|
2670
|
-
},
|
|
2671
|
-
{
|
|
2672
|
-
id: "pub",
|
|
2673
|
-
dependency: { mode: "agent-declares", file: "pubspec.yaml" },
|
|
2674
|
-
installSteps: [{ argv: ["dart", "pub", "get"] }],
|
|
2675
|
-
ingest: {
|
|
2676
|
-
kind: "auto",
|
|
2677
|
-
argv: ["dart", "run", ENTRYPOINT_TOKEN],
|
|
2678
|
-
entrypointExtensions: [".dart"]
|
|
2679
|
-
}
|
|
2680
|
-
}
|
|
2681
|
-
],
|
|
2682
|
-
sdk: {
|
|
2683
|
-
packageName: "algolia_client_search",
|
|
2684
|
-
versionPin: "^1.0.0",
|
|
2685
|
-
docKey: "dart"
|
|
2686
|
-
},
|
|
2687
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.dart`,
|
|
2688
|
-
verification: [
|
|
2689
|
-
{
|
|
2690
|
-
// Gated on the directory it analyzes, not just pubspec.yaml: a run that
|
|
2691
|
-
// only built a search UI never created it, and `dart analyze` on a
|
|
2692
|
-
// missing path fails the whole verification pass.
|
|
2693
|
-
label: "dart analyze",
|
|
2694
|
-
argv: ["dart", "analyze", INGEST_DIR],
|
|
2695
|
-
requiresFile: INGEST_DIR
|
|
2696
|
-
}
|
|
2697
|
-
],
|
|
2698
|
-
envReadInstruction: "Read them from `Platform.environment`.",
|
|
2699
|
-
skipDirs: ["build"]
|
|
2700
|
-
}
|
|
2701
|
-
};
|
|
2702
|
-
var DEFAULT_LANGUAGE_ID = "javascript";
|
|
2703
|
-
var JAVASCRIPT = "javascript";
|
|
2704
|
-
var CURATED_LANGUAGES = Object.values(
|
|
2705
|
-
LANGUAGE_PROFILES
|
|
2706
|
-
).map((profile2) => profile2.displayName);
|
|
2707
|
-
function isBackendLanguage(profile2) {
|
|
2708
|
-
return profile2.id !== JAVASCRIPT;
|
|
2709
|
-
}
|
|
2710
|
-
function normalizeLanguageName(name) {
|
|
2711
|
-
return name.toLowerCase().trim().replace(/[\s_-]+/g, "");
|
|
2712
|
-
}
|
|
2713
|
-
var ALIAS_TO_ID = /* @__PURE__ */ new Map();
|
|
2714
|
-
for (const profile2 of Object.values(LANGUAGE_PROFILES)) {
|
|
2715
|
-
for (const alias of [profile2.id, profile2.displayName, ...profile2.aliases]) {
|
|
2716
|
-
ALIAS_TO_ID.set(normalizeLanguageName(alias), profile2.id);
|
|
2717
|
-
}
|
|
2718
|
-
}
|
|
2719
|
-
function resolveLanguageProfile(name) {
|
|
2720
|
-
const id = ALIAS_TO_ID.get(normalizeLanguageName(name));
|
|
2721
|
-
return id ? LANGUAGE_PROFILES[id] : void 0;
|
|
2722
|
-
}
|
|
2723
|
-
function isSameLanguage(a, b) {
|
|
2724
|
-
const x = resolveLanguageProfile(a);
|
|
2725
|
-
const y = resolveLanguageProfile(b);
|
|
2726
|
-
if (x && y) return x.id === y.id;
|
|
2727
|
-
if (x || y) return false;
|
|
2728
|
-
const folded = normalizeLanguageName(a);
|
|
2729
|
-
return folded !== "" && folded === normalizeLanguageName(b);
|
|
2730
|
-
}
|
|
2731
|
-
var BASE_SKIP_DIRS = ["node_modules", ".git", "dist"];
|
|
2732
|
-
var ALL_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2733
|
-
...BASE_SKIP_DIRS,
|
|
2734
|
-
...Object.values(LANGUAGE_PROFILES).flatMap((p) => p.skipDirs)
|
|
2735
|
-
]);
|
|
2736
|
-
var ALLOWED_BINARIES = new Set(
|
|
2737
|
-
Object.values(LANGUAGE_PROFILES).flatMap((profile2) => [
|
|
2738
|
-
...profile2.packageManagers.flatMap((pm) => [
|
|
2739
|
-
...pm.installSteps.map((s) => s.argv[0]),
|
|
2740
|
-
...pm.ingest.kind === "auto" ? [pm.ingest.argv[0]] : []
|
|
2741
|
-
]),
|
|
2742
|
-
...profile2.verification.map((v) => v.argv[0])
|
|
2743
|
-
])
|
|
2744
|
-
);
|
|
2745
|
-
var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
|
|
2746
|
-
function isWorktreeRelativeCommand(command) {
|
|
2747
|
-
return command.includes("/");
|
|
2748
|
-
}
|
|
2749
|
-
function withCommand(argv, command) {
|
|
2750
|
-
return [command, ...argv.slice(1)];
|
|
2751
|
-
}
|
|
2752
|
-
function resolveDeclaredManifest(root, packageManager) {
|
|
2753
|
-
const { dependency } = packageManager;
|
|
2754
|
-
if (dependency.mode !== "agent-declares" || !dependency.alternatives?.length) {
|
|
2755
|
-
return packageManager;
|
|
2756
|
-
}
|
|
2757
|
-
const present = [dependency.file, ...dependency.alternatives].find(
|
|
2758
|
-
(file) => existsSync2(join9(root, file))
|
|
2759
|
-
);
|
|
2760
|
-
if (!present || present === dependency.file) return packageManager;
|
|
2761
|
-
return { ...packageManager, dependency: { ...dependency, file: present } };
|
|
2762
|
-
}
|
|
2763
|
-
async function manifestPresent(root, manifest, listing) {
|
|
2764
|
-
if (!manifest.startsWith("*.")) return existsSync2(join9(root, manifest));
|
|
2765
|
-
if (!listing.entries) {
|
|
2766
|
-
const entries = await readdir2(root).catch(() => []);
|
|
2767
|
-
listing.entries = Array.isArray(entries) ? entries : [];
|
|
2768
|
-
}
|
|
2769
|
-
const suffix = manifest.slice(1);
|
|
2770
|
-
return listing.entries.some((e) => e.endsWith(suffix));
|
|
2771
|
-
}
|
|
2772
|
-
async function profileManifestPresent(root, profile2, listing) {
|
|
2773
|
-
for (const manifest of profile2.manifests) {
|
|
2774
|
-
if (await manifestPresent(root, manifest, listing)) return true;
|
|
2775
|
-
}
|
|
2776
|
-
return false;
|
|
2777
|
-
}
|
|
2778
|
-
async function detectProfilesFromManifests(root) {
|
|
2779
|
-
const listing = {};
|
|
2780
|
-
const found = [];
|
|
2781
|
-
for (const profile2 of Object.values(LANGUAGE_PROFILES)) {
|
|
2782
|
-
if (await profileManifestPresent(root, profile2, listing)) found.push(profile2);
|
|
2783
|
-
}
|
|
2784
|
-
return found;
|
|
2785
|
-
}
|
|
2786
|
-
async function hasProfileManifest(root, profile2) {
|
|
2787
|
-
return profileManifestPresent(root, profile2, {});
|
|
2788
|
-
}
|
|
2789
|
-
async function pickIngestionCandidates(root, confirmedNames) {
|
|
2790
|
-
const confirmed3 = confirmedNames.map((name) => resolveLanguageProfile(name)).filter((p) => p !== void 0);
|
|
2791
|
-
const onDisk = await detectProfilesFromManifests(root);
|
|
2792
|
-
const onDiskIds = new Set(onDisk.map((p) => p.id));
|
|
2793
|
-
const candidates = [
|
|
2794
|
-
...new Map(
|
|
2795
|
-
confirmed3.filter((p) => onDiskIds.has(p.id)).map((p) => [p.id, p])
|
|
2796
|
-
).values()
|
|
2797
|
-
];
|
|
2798
|
-
return { candidates, confirmed: confirmed3, onDisk };
|
|
2799
|
-
}
|
|
2800
|
-
async function resolveToolchain(root, profile2) {
|
|
2801
|
-
const matched = profile2.packageManagers.find(
|
|
2802
|
-
(pm) => [...pm.lockfiles ?? [], ...pm.detectFiles ?? []].some(
|
|
2803
|
-
(f) => existsSync2(join9(root, f))
|
|
2804
|
-
)
|
|
2805
|
-
);
|
|
2806
|
-
const packageManager = resolveDeclaredManifest(
|
|
2807
|
-
root,
|
|
2808
|
-
matched ?? profile2.packageManagers[0]
|
|
2809
|
-
);
|
|
2810
|
-
let { installSteps, ingest } = packageManager;
|
|
2811
|
-
installSteps = installSteps.map(
|
|
2812
|
-
(step) => isWorktreeRelativeCommand(step.argv[0]) ? { ...step, argv: withCommand(step.argv, join9(root, step.argv[0])) } : step
|
|
2813
|
-
);
|
|
2814
|
-
if (ingest.kind === "auto" && isWorktreeRelativeCommand(ingest.argv[0])) {
|
|
2815
|
-
ingest = {
|
|
2816
|
-
...ingest,
|
|
2817
|
-
argv: withCommand(ingest.argv, join9(root, ingest.argv[0]))
|
|
2818
|
-
};
|
|
2819
|
-
}
|
|
2820
|
-
if (profile2.id === "javascript") {
|
|
2821
|
-
const pm = await detectPackageManager(root);
|
|
2822
|
-
if (JS_PACKAGE_MANAGERS.has(pm)) {
|
|
2823
|
-
installSteps = installSteps.map((step) => ({
|
|
2824
|
-
...step,
|
|
2825
|
-
argv: withCommand(step.argv, pm)
|
|
2826
|
-
}));
|
|
2827
|
-
if (pm === "bun" && ingest.kind === "auto") {
|
|
2828
|
-
ingest = { ...ingest, argv: withCommand(ingest.argv, "bun") };
|
|
2829
|
-
}
|
|
2830
|
-
}
|
|
2831
|
-
}
|
|
2832
|
-
return { profile: profile2, packageManager, installSteps, ingest };
|
|
2833
|
-
}
|
|
2834
|
-
function resolveIngestArgv(ingest, entrypoint) {
|
|
2835
|
-
if (ingest.kind !== "auto") {
|
|
2836
|
-
throw new Error("resolveIngestArgv called for a manual-run toolchain");
|
|
2837
|
-
}
|
|
2838
|
-
return ingest.argv.map(
|
|
2839
|
-
(part) => part === ENTRYPOINT_TOKEN ? entrypoint : part
|
|
2840
|
-
);
|
|
2841
|
-
}
|
|
2842
|
-
function describeIngestCommand(ingest, entrypoint) {
|
|
2843
|
-
if (ingest.kind !== "auto") return ingest.runCommand;
|
|
2844
|
-
return ingest.argv.map((part) => part === ENTRYPOINT_TOKEN ? shellQuote(entrypoint) : part).join(" ");
|
|
2845
|
-
}
|
|
2846
|
-
function ingestScriptDir(profile2) {
|
|
2847
|
-
const parts = profile2.ingestEntrypointExample.split("/");
|
|
2848
|
-
return parts.slice(0, -1).join("/") || ".";
|
|
2849
|
-
}
|
|
2850
|
-
function dependencyInstruction(toolchain) {
|
|
2851
|
-
const { profile: profile2, packageManager } = toolchain;
|
|
2852
|
-
const { packageName, versionPin } = profile2.sdk;
|
|
2853
|
-
const also = profile2.sdk.alsoRequires ? ` ${profile2.sdk.alsoRequires}` : "";
|
|
2854
|
-
switch (packageManager.dependency.mode) {
|
|
2855
|
-
case "wizard-installs":
|
|
2856
|
-
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}`;
|
|
2857
|
-
case "code-imports":
|
|
2858
|
-
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}`;
|
|
2859
|
-
case "agent-declares":
|
|
2860
|
-
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}`;
|
|
2861
|
-
}
|
|
2862
|
-
}
|
|
2863
|
-
|
|
2864
|
-
// src/lib/tools/searchFiles.ts
|
|
2865
2160
|
var MAX_QUERY_LENGTH = 1e3;
|
|
2866
2161
|
async function walkFiles(dir) {
|
|
2162
|
+
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
2867
2163
|
const out = [];
|
|
2868
|
-
for (const e of await
|
|
2869
|
-
if (e.name.startsWith(".") ||
|
|
2870
|
-
const full =
|
|
2164
|
+
for (const e of await readdir2(dir, { withFileTypes: true })) {
|
|
2165
|
+
if (e.name.startsWith(".") || skip.has(e.name)) continue;
|
|
2166
|
+
const full = join8(dir, e.name);
|
|
2871
2167
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2872
2168
|
else if (e.isFile()) out.push(full);
|
|
2873
2169
|
}
|
|
@@ -2900,7 +2196,7 @@ function searchFilesTool(ctx) {
|
|
|
2900
2196
|
for (const file of await walkFiles(resolved.target)) {
|
|
2901
2197
|
let content;
|
|
2902
2198
|
try {
|
|
2903
|
-
content = await
|
|
2199
|
+
content = await readFile6(file, "utf8");
|
|
2904
2200
|
} catch {
|
|
2905
2201
|
continue;
|
|
2906
2202
|
}
|
|
@@ -2924,146 +2220,88 @@ function searchFilesTool(ctx) {
|
|
|
2924
2220
|
import { tool as tool8 } from "ai";
|
|
2925
2221
|
import z11 from "zod";
|
|
2926
2222
|
|
|
2927
|
-
// src/lib/tools/repoVerification.ts
|
|
2928
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
2929
|
-
import { join as join11 } from "node:path";
|
|
2930
|
-
|
|
2931
2223
|
// src/lib/tools/utils/runCommand.ts
|
|
2932
2224
|
import { spawn as spawn2 } from "node:child_process";
|
|
2933
|
-
|
|
2934
|
-
var INGEST_TIMEOUT_MS = 15 * 6e4;
|
|
2935
|
-
var VERIFY_TIMEOUT_MS = 10 * 6e4;
|
|
2936
|
-
var KILL_GRACE_MS = 5e3;
|
|
2937
|
-
function runCommand(command, args, options = {}) {
|
|
2938
|
-
const { cwd, env, timeoutMs = VERIFY_TIMEOUT_MS } = options;
|
|
2225
|
+
function runCommand(command, args, cwd) {
|
|
2939
2226
|
return new Promise((resolve4) => {
|
|
2940
2227
|
let output = "";
|
|
2941
|
-
let settled = false;
|
|
2942
2228
|
const child = spawn2(command, args, {
|
|
2943
2229
|
cwd,
|
|
2944
|
-
|
|
2945
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2946
|
-
...env ? { env: { ...process.env, ...env } } : {}
|
|
2230
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2947
2231
|
});
|
|
2948
|
-
const settle = (result) => {
|
|
2949
|
-
if (settled) return;
|
|
2950
|
-
settled = true;
|
|
2951
|
-
clearTimeout(timer);
|
|
2952
|
-
resolve4(result);
|
|
2953
|
-
};
|
|
2954
|
-
const timer = setTimeout(() => {
|
|
2955
|
-
child.kill("SIGTERM");
|
|
2956
|
-
setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
|
|
2957
|
-
const seconds = Math.round(timeoutMs / 1e3);
|
|
2958
|
-
settle({
|
|
2959
|
-
code: 1,
|
|
2960
|
-
output: `${output}
|
|
2961
|
-
Timed out after ${seconds}s: ${command} ${args.join(" ")}`.trim(),
|
|
2962
|
-
timedOut: true
|
|
2963
|
-
});
|
|
2964
|
-
}, timeoutMs);
|
|
2965
2232
|
child.stdout?.on("data", (d) => output += d);
|
|
2966
2233
|
child.stderr?.on("data", (d) => output += d);
|
|
2967
2234
|
child.on(
|
|
2968
2235
|
"error",
|
|
2969
|
-
(err) =>
|
|
2970
|
-
code: 1,
|
|
2971
|
-
output: `Failed to run ${command}: ${err.message}`,
|
|
2972
|
-
timedOut: false
|
|
2973
|
-
})
|
|
2974
|
-
);
|
|
2975
|
-
child.on(
|
|
2976
|
-
"close",
|
|
2977
|
-
(code) => settle({ code: code ?? 1, output, timedOut: false })
|
|
2236
|
+
(err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
|
|
2978
2237
|
);
|
|
2238
|
+
child.on("close", (code) => resolve4({ code: code ?? 1, output }));
|
|
2979
2239
|
});
|
|
2980
2240
|
}
|
|
2981
2241
|
|
|
2242
|
+
// src/lib/tools/utils/packageManager.ts
|
|
2243
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
2244
|
+
import { existsSync } from "node:fs";
|
|
2245
|
+
import { join as join9 } from "node:path";
|
|
2246
|
+
var LOCKFILES = [
|
|
2247
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
2248
|
+
["yarn.lock", "yarn"],
|
|
2249
|
+
["bun.lockb", "bun"],
|
|
2250
|
+
["bun.lock", "bun"],
|
|
2251
|
+
["package-lock.json", "npm"]
|
|
2252
|
+
];
|
|
2253
|
+
async function readPackageJson(cwd = process.cwd()) {
|
|
2254
|
+
return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
|
|
2255
|
+
}
|
|
2256
|
+
function packageManagerFrom(pkg) {
|
|
2257
|
+
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2258
|
+
}
|
|
2259
|
+
function packageManagerFromLockfile(cwd) {
|
|
2260
|
+
return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
|
|
2261
|
+
}
|
|
2262
|
+
async function detectPackageManager(cwd) {
|
|
2263
|
+
try {
|
|
2264
|
+
const pkg = await readPackageJson(cwd);
|
|
2265
|
+
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2266
|
+
} catch {
|
|
2267
|
+
}
|
|
2268
|
+
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2982
2271
|
// src/lib/tools/repoVerification.ts
|
|
2983
2272
|
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
2984
|
-
async function
|
|
2985
|
-
const { code, output } = await runCommand(binary, args, {
|
|
2986
|
-
timeoutMs: VERIFY_TIMEOUT_MS
|
|
2987
|
-
});
|
|
2988
|
-
return { command, exitCode: code, ok: code === 0, output: output.trim() };
|
|
2989
|
-
}
|
|
2990
|
-
async function javascriptChecks() {
|
|
2273
|
+
async function runRepoVerificationCheck() {
|
|
2991
2274
|
let pkg;
|
|
2992
2275
|
try {
|
|
2993
2276
|
pkg = await readPackageJson();
|
|
2994
2277
|
} catch (err) {
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
};
|
|
2278
|
+
const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
|
|
2279
|
+
return { ok: false, checks: [], limitation };
|
|
2998
2280
|
}
|
|
2999
2281
|
const scripts = pkg.scripts ?? {};
|
|
3000
2282
|
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
3001
2283
|
if (present.length === 0) {
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
};
|
|
2284
|
+
const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
|
|
2285
|
+
return { ok: false, checks: [], limitation };
|
|
3005
2286
|
}
|
|
3006
2287
|
const pm = await detectPackageManager(process.cwd());
|
|
3007
2288
|
const checks = [];
|
|
3008
2289
|
for (const script of present) {
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
);
|
|
3012
|
-
}
|
|
3013
|
-
return { checks };
|
|
3014
|
-
}
|
|
3015
|
-
async function runRepoVerificationCheck(languages = [DEFAULT_LANGUAGE_ID]) {
|
|
3016
|
-
const ids = [...new Set(languages)];
|
|
3017
|
-
if (ids.length === 0) ids.push(DEFAULT_LANGUAGE_ID);
|
|
3018
|
-
const checks = [];
|
|
3019
|
-
const limitations = [];
|
|
3020
|
-
for (const id of ids) {
|
|
3021
|
-
if (id === JAVASCRIPT) {
|
|
3022
|
-
const result = await javascriptChecks();
|
|
3023
|
-
if ("checks" in result) checks.push(...result.checks);
|
|
3024
|
-
else limitations.push(result.limitation);
|
|
3025
|
-
continue;
|
|
3026
|
-
}
|
|
3027
|
-
const profile2 = LANGUAGE_PROFILES[id];
|
|
3028
|
-
const runnable = profile2.verification.filter(
|
|
3029
|
-
(spec) => !spec.requiresFile || existsSync3(join11(process.cwd(), spec.requiresFile))
|
|
3030
|
-
);
|
|
3031
|
-
if (runnable.length === 0) {
|
|
3032
|
-
limitations.push(
|
|
3033
|
-
`No mechanical verification available for ${profile2.displayName} in this repo.`
|
|
3034
|
-
);
|
|
3035
|
-
continue;
|
|
3036
|
-
}
|
|
3037
|
-
for (const spec of runnable) {
|
|
3038
|
-
checks.push(
|
|
3039
|
-
await runCheck(spec.argv.join(" "), spec.argv[0], [
|
|
3040
|
-
...spec.argv.slice(1)
|
|
3041
|
-
])
|
|
3042
|
-
);
|
|
3043
|
-
}
|
|
2290
|
+
const command = `${pm} run ${script}`;
|
|
2291
|
+
const { code, output } = await runCommand(pm, ["run", script]);
|
|
2292
|
+
checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
|
|
3044
2293
|
}
|
|
3045
|
-
|
|
3046
|
-
return {
|
|
3047
|
-
ok: false,
|
|
3048
|
-
checks: [],
|
|
3049
|
-
limitation: limitations.join(" ") || "No verification checks available."
|
|
3050
|
-
};
|
|
3051
|
-
}
|
|
3052
|
-
return {
|
|
3053
|
-
ok: checks.every((c) => c.ok),
|
|
3054
|
-
checks,
|
|
3055
|
-
...limitations.length ? { limitation: limitations.join(" ") } : {}
|
|
3056
|
-
};
|
|
2294
|
+
return { ok: checks.every((c) => c.ok), checks };
|
|
3057
2295
|
}
|
|
3058
2296
|
|
|
3059
2297
|
// src/lib/tools/verifyImplementation.ts
|
|
3060
|
-
function verifyImplementationTool(
|
|
2298
|
+
function verifyImplementationTool() {
|
|
3061
2299
|
return tool8({
|
|
3062
|
-
description: "Run the repo's mechanical verification
|
|
2300
|
+
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.",
|
|
3063
2301
|
inputSchema: z11.object(),
|
|
3064
2302
|
execute: async () => {
|
|
3065
|
-
logger.info(
|
|
3066
|
-
return runRepoVerificationCheck(
|
|
2303
|
+
logger.info("called verifyImplementation tool");
|
|
2304
|
+
return runRepoVerificationCheck();
|
|
3067
2305
|
}
|
|
3068
2306
|
});
|
|
3069
2307
|
}
|
|
@@ -3189,17 +2427,12 @@ var DEFAULT_TOOL_LIMITS = {
|
|
|
3189
2427
|
read: 20,
|
|
3190
2428
|
match: 100
|
|
3191
2429
|
};
|
|
3192
|
-
function createToolContext({
|
|
3193
|
-
limits = DEFAULT_TOOL_LIMITS,
|
|
3194
|
-
cwd = process.cwd(),
|
|
3195
|
-
languages = [DEFAULT_LANGUAGE_ID]
|
|
3196
|
-
} = {}) {
|
|
2430
|
+
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
|
|
3197
2431
|
return {
|
|
3198
2432
|
root: cwd,
|
|
3199
2433
|
cwd,
|
|
3200
2434
|
limits,
|
|
3201
|
-
counts: { list: 0, search: 0, read: 0 }
|
|
3202
|
-
languages: languages.length ? languages : [DEFAULT_LANGUAGE_ID]
|
|
2435
|
+
counts: { list: 0, search: 0, read: 0 }
|
|
3203
2436
|
};
|
|
3204
2437
|
}
|
|
3205
2438
|
|
|
@@ -3236,7 +2469,7 @@ function createTools(ctx, { output, tools }) {
|
|
|
3236
2469
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
3237
2470
|
verifyImplementation: withLogging(
|
|
3238
2471
|
"verifyImplementation",
|
|
3239
|
-
verifyImplementationTool(
|
|
2472
|
+
verifyImplementationTool()
|
|
3240
2473
|
),
|
|
3241
2474
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
3242
2475
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
@@ -3272,7 +2505,7 @@ async function runAgent(req) {
|
|
|
3272
2505
|
baseURL: PROXY_BASE_URL,
|
|
3273
2506
|
fetch: proxyFetch
|
|
3274
2507
|
});
|
|
3275
|
-
const toolContext = createToolContext(
|
|
2508
|
+
const toolContext = createToolContext();
|
|
3276
2509
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
3277
2510
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
3278
2511
|
const instructions = [
|
|
@@ -3363,11 +2596,8 @@ var detectLanguageSchema = z16.object({
|
|
|
3363
2596
|
var detectLanguage = () => runAgent({
|
|
3364
2597
|
instructions: [
|
|
3365
2598
|
"Analyze the codebase and determine the programming languages and frameworks used",
|
|
3366
|
-
"
|
|
3367
|
-
"List the language that owns the backend/data code first \u2014 that is the one an ingestion script will be written in.",
|
|
3368
|
-
"If a superset language is found, exclude the subset language. TS-over-JS. Kotlin-over-Java when Kotlin is primary.",
|
|
2599
|
+
"If a superset language is found, exclude the subset language. TS-over-JS.",
|
|
3369
2600
|
"If a meta-framework is used, exclude the framework. Next-over-React.",
|
|
3370
|
-
"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).",
|
|
3371
2601
|
"Return the exact version",
|
|
3372
2602
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
3373
2603
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
@@ -3416,7 +2646,6 @@ var MODE_CONFIG = {
|
|
|
3416
2646
|
"Analyze the codebase to find the data entities (models) that should be ingested into Algolia.",
|
|
3417
2647
|
"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).",
|
|
3418
2648
|
"Inspect the source of each entity to extract real field names for attributes \u2014 do not guess or leave attributes empty.",
|
|
3419
|
-
"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.",
|
|
3420
2649
|
"Prefer domain models (e.g. Document, Product, User) over framework or infrastructure types.",
|
|
3421
2650
|
"Use as few tools as possible, but do not guess. If you cannot find any entities, return an empty array.",
|
|
3422
2651
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
@@ -3428,9 +2657,8 @@ var MODE_CONFIG = {
|
|
|
3428
2657
|
instructions: [
|
|
3429
2658
|
"Analyze the codebase to determine the single best location to add search UI functionality.",
|
|
3430
2659
|
"Prefer a shared, always-rendered layout location (e.g. a header or navigation component) so search is reachable across the app.",
|
|
3431
|
-
"
|
|
3432
|
-
|
|
3433
|
-
'Use as few tools as possible, but do not guess. If the project renders no UI at all (an API-only service), say "unknown".',
|
|
2660
|
+
"Return one file path as searchImplementationAnalysis (e.g. /layouts/header.tsx).",
|
|
2661
|
+
'Use as few tools as possible, but do not guess. If you cannot find a clear location, say "unknown".',
|
|
3434
2662
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
3435
2663
|
"When done, call reportStatus"
|
|
3436
2664
|
],
|
|
@@ -3439,8 +2667,8 @@ var MODE_CONFIG = {
|
|
|
3439
2667
|
verification: {
|
|
3440
2668
|
instructions: [
|
|
3441
2669
|
"Analyze the codebase to determine which code-quality tools are available to validate changes.",
|
|
3442
|
-
"Look at
|
|
3443
|
-
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"]
|
|
2670
|
+
"Look at package.json scripts, config files (e.g. .eslintrc, tsconfig, prettier), and dev dependencies.",
|
|
2671
|
+
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"].',
|
|
3444
2672
|
"Use as few tools as possible, but do not guess. If you cannot find any, return an empty array.",
|
|
3445
2673
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
3446
2674
|
"When done, call reportStatus"
|
|
@@ -3467,7 +2695,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3467
2695
|
// package.json
|
|
3468
2696
|
var package_default = {
|
|
3469
2697
|
name: "@algolia/wizard",
|
|
3470
|
-
version: "0.6.0
|
|
2698
|
+
version: "0.6.0",
|
|
3471
2699
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3472
2700
|
type: "module",
|
|
3473
2701
|
engines: {
|
|
@@ -3570,185 +2798,82 @@ function parseEntries(raw) {
|
|
|
3570
2798
|
return raw.split(",").map(clean).filter(Boolean).slice(0, MAX_ENTRIES).map((name) => ({ name, version: "unknown" }));
|
|
3571
2799
|
}
|
|
3572
2800
|
var summarize = (entries) => entries.length ? entries.map((e) => e.name).join(", ") : "none";
|
|
3573
|
-
|
|
3574
|
-
// src/actions/confirmLanguage.ts
|
|
3575
|
-
import z19 from "zod";
|
|
3576
|
-
var confirmLanguageSchema = z19.object({
|
|
3577
|
-
languages: detectLanguageSchema.shape.languages
|
|
3578
|
-
});
|
|
3579
|
-
var OTHER_OPTION = "Other";
|
|
3580
|
-
function confirmed(languages) {
|
|
3581
|
-
track("AI Wizard Language Confirmed", { languages });
|
|
3582
|
-
return { languages };
|
|
3583
|
-
}
|
|
3584
|
-
async function askOtherLanguage(ctx) {
|
|
3585
|
-
let prompt = "enter the language for your ingestion script";
|
|
2801
|
+
async function askList(ctx, prompt, { required = false } = {}) {
|
|
3586
2802
|
for (; ; ) {
|
|
3587
2803
|
const answer = await ctx.requestUserInput({
|
|
3588
2804
|
prompt,
|
|
3589
2805
|
promptType: "textInput",
|
|
3590
|
-
options: []
|
|
2806
|
+
options: [],
|
|
2807
|
+
helpText: 'Comma-separated, e.g. "TypeScript, Node".'
|
|
3591
2808
|
});
|
|
3592
2809
|
if (typeof answer !== "string") {
|
|
3593
|
-
throw new Error("
|
|
2810
|
+
throw new Error("askList received an unexpected non-text result");
|
|
3594
2811
|
}
|
|
3595
|
-
const
|
|
3596
|
-
if (
|
|
3597
|
-
prompt = "
|
|
2812
|
+
const entries = parseEntries(answer);
|
|
2813
|
+
if (entries.length || !required) return entries;
|
|
2814
|
+
prompt = "Please enter at least one entry:";
|
|
3598
2815
|
}
|
|
3599
2816
|
}
|
|
2817
|
+
|
|
2818
|
+
// src/actions/confirmLanguage.ts
|
|
2819
|
+
import z19 from "zod";
|
|
2820
|
+
var confirmLanguageSchema = z19.object({
|
|
2821
|
+
languages: detectLanguageSchema.shape.languages
|
|
2822
|
+
});
|
|
3600
2823
|
async function confirmLanguage(ctx) {
|
|
3601
2824
|
const detected = ctx.getStepOutput("project-scan");
|
|
3602
|
-
const
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
prompt: `Write the ingestion script in ${primary.name}?`,
|
|
3608
|
-
promptType: "acceptReject",
|
|
3609
|
-
options: [`Confirm ${primary.name}`, "Use a different language"],
|
|
3610
|
-
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0],
|
|
3611
|
-
messages: detectedLanguages.length > 1 ? [`Detected: ${summarize(detectedLanguages)}`] : []
|
|
3612
|
-
});
|
|
3613
|
-
if (accepted === true) return confirmed(detectedLanguages);
|
|
3614
|
-
}
|
|
3615
|
-
const options = [...CURATED_LANGUAGES];
|
|
3616
|
-
for (const language of detectedLanguages) {
|
|
3617
|
-
if (!options.some((o) => isSameLanguage(o, language.name))) {
|
|
3618
|
-
options.push(language.name);
|
|
3619
|
-
}
|
|
3620
|
-
}
|
|
3621
|
-
options.push(OTHER_OPTION);
|
|
3622
|
-
const detectedFor = (option) => detectedLanguages.find((l) => isSameLanguage(option, l.name));
|
|
3623
|
-
const secondary = options.map(
|
|
3624
|
-
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
3625
|
-
);
|
|
3626
|
-
const defaultSelectedIndex = Math.max(
|
|
3627
|
-
options.findIndex((o) => detectedFor(o)),
|
|
3628
|
-
0
|
|
3629
|
-
);
|
|
3630
|
-
const selection = await ctx.requestUserInput({
|
|
3631
|
-
prompt: "select the language for your ingestion script",
|
|
3632
|
-
promptType: "multipleChoice",
|
|
3633
|
-
options,
|
|
3634
|
-
secondary,
|
|
3635
|
-
defaultSelectedIndex
|
|
2825
|
+
const answer = await ctx.requestUserInput({
|
|
2826
|
+
prompt: "Did we detect your language(s) correctly?",
|
|
2827
|
+
promptType: "acceptReject",
|
|
2828
|
+
options: ["Yes", "No"],
|
|
2829
|
+
messages: [`Languages: ${summarize(detected.languages)}`]
|
|
3636
2830
|
});
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
}
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
2831
|
+
const languages = answer === true ? detected.languages : await askList(ctx, "List the languages your project uses:", {
|
|
2832
|
+
required: true
|
|
2833
|
+
});
|
|
2834
|
+
track("AI Wizard Language Confirmed", {
|
|
2835
|
+
languages
|
|
2836
|
+
});
|
|
2837
|
+
return { languages };
|
|
3643
2838
|
}
|
|
3644
2839
|
|
|
3645
2840
|
// src/actions/confirmFramework.ts
|
|
3646
2841
|
import z20 from "zod";
|
|
3647
|
-
|
|
3648
|
-
// src/lib/frameworks.ts
|
|
3649
|
-
var BACKEND_ONLY_FRAMEWORK = "Backend only / API";
|
|
3650
|
-
var FRAMEWORKS = [
|
|
3651
|
-
// Frontend — InstantSearch component flavors.
|
|
3652
|
-
{ name: "Next.js", strategy: "react", aliases: ["next", "nextjs"] },
|
|
3653
|
-
{ name: "React", strategy: "react", aliases: ["reactjs"] },
|
|
3654
|
-
{ name: "Vue", strategy: "vue", aliases: ["vuejs", "nuxt", "nuxtjs"] },
|
|
3655
|
-
{ name: "Angular", strategy: "angular", aliases: ["angularjs"] },
|
|
3656
|
-
// No Svelte InstantSearch flavor exists, so it uses InstantSearch.js.
|
|
3657
|
-
{ name: "Svelte", strategy: "js", aliases: ["sveltekit"] },
|
|
3658
|
-
{
|
|
3659
|
-
name: "Vanilla JS",
|
|
3660
|
-
strategy: "js",
|
|
3661
|
-
aliases: ["vanilla", "javascript", "js", "astro", "vite"]
|
|
3662
|
-
},
|
|
3663
|
-
// Backend — Algolia's official framework integrations. Server-rendered
|
|
3664
|
-
// templates get InstantSearch.js from a CDN.
|
|
3665
|
-
{
|
|
3666
|
-
name: "Rails",
|
|
3667
|
-
strategy: "cdn-template",
|
|
3668
|
-
aliases: ["rubyonrails", "ruby on rails", "erb"]
|
|
3669
|
-
},
|
|
3670
|
-
{ name: "Django", strategy: "cdn-template", aliases: ["jinja", "jinja2"] },
|
|
3671
|
-
{ name: "Laravel", strategy: "cdn-template", aliases: ["blade"] },
|
|
3672
|
-
{ name: "Symfony", strategy: "cdn-template", aliases: ["twig"] },
|
|
3673
|
-
// Mobile — Algolia ships InstantSearch iOS/Android and Dart clients, but the
|
|
3674
|
-
// wizard can't scaffold a native UI, so it points at the docs instead.
|
|
3675
|
-
{ name: "Flutter", strategy: "none", aliases: [] },
|
|
3676
|
-
{ name: "iOS", strategy: "none", aliases: ["swiftui", "uikit"] },
|
|
3677
|
-
{ name: "Android", strategy: "none", aliases: ["jetpack compose", "compose"] },
|
|
3678
|
-
{ name: BACKEND_ONLY_FRAMEWORK, strategy: "cdn-template", aliases: [] }
|
|
3679
|
-
];
|
|
3680
|
-
var CURATED_FRAMEWORKS = FRAMEWORKS.map(
|
|
3681
|
-
(f) => f.name
|
|
3682
|
-
);
|
|
3683
|
-
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3684
|
-
var ALIAS_TO_NAME = /* @__PURE__ */ new Map();
|
|
3685
|
-
for (const framework of FRAMEWORKS) {
|
|
3686
|
-
for (const alias of [framework.name, ...framework.aliases]) {
|
|
3687
|
-
ALIAS_TO_NAME.set(normalize(alias), framework.name);
|
|
3688
|
-
}
|
|
3689
|
-
}
|
|
3690
|
-
var STRATEGY_BY_NAME = new Map(FRAMEWORKS.map((f) => [f.name, f.strategy]));
|
|
3691
|
-
function canonicalFrameworkName(name) {
|
|
3692
|
-
return ALIAS_TO_NAME.get(normalize(name));
|
|
3693
|
-
}
|
|
3694
|
-
function isSameFramework(a, b) {
|
|
3695
|
-
const x = canonicalFrameworkName(a) ?? normalize(a);
|
|
3696
|
-
const y = canonicalFrameworkName(b) ?? normalize(b);
|
|
3697
|
-
return x !== "" && x === y;
|
|
3698
|
-
}
|
|
3699
|
-
function resolveSearchStrategy(frameworkName, hasJavaScriptInStack) {
|
|
3700
|
-
const canonical = frameworkName ? canonicalFrameworkName(frameworkName) : void 0;
|
|
3701
|
-
const strategy = canonical ? STRATEGY_BY_NAME.get(canonical) : void 0;
|
|
3702
|
-
if (strategy) return strategy;
|
|
3703
|
-
return hasJavaScriptInStack ? "js" : "cdn-template";
|
|
3704
|
-
}
|
|
3705
|
-
function searchDocKey(strategy) {
|
|
3706
|
-
return strategy === "cdn-template" ? "templates" : strategy;
|
|
3707
|
-
}
|
|
3708
|
-
function bundlesJavaScript(strategy) {
|
|
3709
|
-
return strategy !== "cdn-template" && strategy !== "none";
|
|
3710
|
-
}
|
|
3711
|
-
function canScaffoldSearchUI(strategy) {
|
|
3712
|
-
return strategy !== "none";
|
|
3713
|
-
}
|
|
3714
|
-
var ENV_PREFIXES = [
|
|
3715
|
-
{ aliases: ["next", "nextjs"], prefix: "NEXT_PUBLIC_" },
|
|
3716
|
-
{ aliases: ["nuxt", "nuxtjs"], prefix: "NUXT_PUBLIC_" },
|
|
3717
|
-
{ aliases: ["astro"], prefix: "PUBLIC_" },
|
|
3718
|
-
{ aliases: ["vite"], prefix: "VITE_" }
|
|
3719
|
-
];
|
|
3720
|
-
var DEFAULT_ENV_PREFIX = "PUBLIC_";
|
|
3721
|
-
function publicEnvPrefix(frameworkNames, strategy) {
|
|
3722
|
-
if (!bundlesJavaScript(strategy)) return "";
|
|
3723
|
-
const present = new Set(frameworkNames.map(normalize));
|
|
3724
|
-
for (const { aliases, prefix } of ENV_PREFIXES) {
|
|
3725
|
-
if (aliases.some((alias) => present.has(alias))) return prefix;
|
|
3726
|
-
}
|
|
3727
|
-
return DEFAULT_ENV_PREFIX;
|
|
3728
|
-
}
|
|
3729
|
-
function describeSearchTarget(strategy, frameworkName) {
|
|
3730
|
-
switch (strategy) {
|
|
3731
|
-
case "react":
|
|
3732
|
-
return "React (react-instantsearch)";
|
|
3733
|
-
case "vue":
|
|
3734
|
-
return "Vue (vue-instantsearch)";
|
|
3735
|
-
case "angular":
|
|
3736
|
-
return "Angular (angular-instantsearch)";
|
|
3737
|
-
case "js":
|
|
3738
|
-
return "plain JavaScript (InstantSearch.js)";
|
|
3739
|
-
case "cdn-template":
|
|
3740
|
-
return `${frameworkName ?? "server-rendered"} templates (InstantSearch.js via CDN)`;
|
|
3741
|
-
case "none":
|
|
3742
|
-
return frameworkName ?? "a native mobile app";
|
|
3743
|
-
}
|
|
3744
|
-
}
|
|
3745
|
-
|
|
3746
|
-
// src/actions/confirmFramework.ts
|
|
3747
2842
|
var confirmFrameworkSchema = z20.object({
|
|
3748
2843
|
frameworks: detectLanguageSchema.shape.frameworks
|
|
3749
2844
|
});
|
|
3750
|
-
var
|
|
3751
|
-
|
|
2845
|
+
var CURATED_FRAMEWORKS = [
|
|
2846
|
+
"Next.js",
|
|
2847
|
+
"React",
|
|
2848
|
+
"Vue",
|
|
2849
|
+
"Angular",
|
|
2850
|
+
"Svelte",
|
|
2851
|
+
"Vanilla JS"
|
|
2852
|
+
];
|
|
2853
|
+
var OTHER_OPTION = "Other";
|
|
2854
|
+
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2855
|
+
var FRAMEWORK_ALIASES = {
|
|
2856
|
+
next: "nextjs",
|
|
2857
|
+
nextjs: "nextjs",
|
|
2858
|
+
react: "react",
|
|
2859
|
+
reactjs: "react",
|
|
2860
|
+
vue: "vue",
|
|
2861
|
+
vuejs: "vue",
|
|
2862
|
+
angular: "angular",
|
|
2863
|
+
angularjs: "angular",
|
|
2864
|
+
svelte: "svelte",
|
|
2865
|
+
sveltekit: "svelte",
|
|
2866
|
+
vanillajs: "vanillajs",
|
|
2867
|
+
vanilla: "vanillajs",
|
|
2868
|
+
javascript: "vanillajs",
|
|
2869
|
+
js: "vanillajs"
|
|
2870
|
+
};
|
|
2871
|
+
var isSameFramework = (a, b) => {
|
|
2872
|
+
const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
|
|
2873
|
+
const y = FRAMEWORK_ALIASES[normalize(b)] ?? normalize(b);
|
|
2874
|
+
return x !== "" && x === y;
|
|
2875
|
+
};
|
|
2876
|
+
function confirmed(name, version) {
|
|
3752
2877
|
const frameworks = [{ name, version: version ?? "unknown" }];
|
|
3753
2878
|
track("AI Wizard Frontend Framework Confirmed", { frameworks });
|
|
3754
2879
|
return { frameworks };
|
|
@@ -3776,7 +2901,7 @@ async function confirmFramework(ctx) {
|
|
|
3776
2901
|
for (const fw of detectedFrameworks) {
|
|
3777
2902
|
if (!options.some((o) => isSameFramework(o, fw.name))) options.push(fw.name);
|
|
3778
2903
|
}
|
|
3779
|
-
options.push(
|
|
2904
|
+
options.push(OTHER_OPTION);
|
|
3780
2905
|
const detectedFor = (option) => detectedFrameworks.find((fw) => isSameFramework(option, fw.name));
|
|
3781
2906
|
const primary = detectedFrameworks[0];
|
|
3782
2907
|
if (primary) {
|
|
@@ -3786,7 +2911,7 @@ async function confirmFramework(ctx) {
|
|
|
3786
2911
|
options: [`Confirm ${primary.name}`, "Use a different framework"],
|
|
3787
2912
|
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0]
|
|
3788
2913
|
});
|
|
3789
|
-
if (accepted === true) return
|
|
2914
|
+
if (accepted === true) return confirmed(primary.name, primary.version);
|
|
3790
2915
|
}
|
|
3791
2916
|
const secondary = options.map(
|
|
3792
2917
|
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
@@ -3796,7 +2921,7 @@ async function confirmFramework(ctx) {
|
|
|
3796
2921
|
0
|
|
3797
2922
|
);
|
|
3798
2923
|
const selection = await ctx.requestUserInput({
|
|
3799
|
-
prompt: "select
|
|
2924
|
+
prompt: "select a framework",
|
|
3800
2925
|
promptType: "multipleChoice",
|
|
3801
2926
|
options,
|
|
3802
2927
|
secondary,
|
|
@@ -3805,10 +2930,10 @@ async function confirmFramework(ctx) {
|
|
|
3805
2930
|
if (typeof selection !== "string") {
|
|
3806
2931
|
throw new Error("confirmFramework received an unexpected non-text result");
|
|
3807
2932
|
}
|
|
3808
|
-
if (selection ===
|
|
3809
|
-
return
|
|
2933
|
+
if (selection === OTHER_OPTION) {
|
|
2934
|
+
return confirmed(await askOtherFramework(ctx));
|
|
3810
2935
|
}
|
|
3811
|
-
return
|
|
2936
|
+
return confirmed(selection, detectedFor(selection)?.version);
|
|
3812
2937
|
}
|
|
3813
2938
|
|
|
3814
2939
|
// src/actions/promptUser.ts
|
|
@@ -3901,15 +3026,15 @@ async function confirmEntities(ctx) {
|
|
|
3901
3026
|
onSubmit: () => {
|
|
3902
3027
|
}
|
|
3903
3028
|
});
|
|
3904
|
-
const
|
|
3905
|
-
if (
|
|
3029
|
+
const confirmed2 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
|
|
3030
|
+
if (confirmed2.length === 0) {
|
|
3906
3031
|
throw new Error("User cancelled entity selection \u2014 analysis halted.");
|
|
3907
3032
|
}
|
|
3908
|
-
ctx.setUserInput("confirmedEntities",
|
|
3033
|
+
ctx.setUserInput("confirmedEntities", confirmed2);
|
|
3909
3034
|
track("AI Wizard Entities Confirmed", {
|
|
3910
|
-
entities: toEntitySummary(
|
|
3035
|
+
entities: toEntitySummary(confirmed2)
|
|
3911
3036
|
});
|
|
3912
|
-
return { ingestionAnalysis: entities, confirmedEntities:
|
|
3037
|
+
return { ingestionAnalysis: entities, confirmedEntities: confirmed2 };
|
|
3913
3038
|
}
|
|
3914
3039
|
|
|
3915
3040
|
// src/actions/review.ts
|
|
@@ -3933,7 +3058,7 @@ ${JSON.stringify(s.output, null, 2)}`
|
|
|
3933
3058
|
}
|
|
3934
3059
|
function formatReviewSummary(result) {
|
|
3935
3060
|
const nextStepLines = result.nextSteps.map((step) => {
|
|
3936
|
-
const isIngestCommand = step.includes("algolia-wizard/
|
|
3061
|
+
const isIngestCommand = step.includes(".algolia-wizard/ingest.sh");
|
|
3937
3062
|
const isWorktreeCommand = step.includes("/worktrees/");
|
|
3938
3063
|
return {
|
|
3939
3064
|
text: `\u2192 ${step}`,
|
|
@@ -3975,14 +3100,13 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3975
3100
|
import z24 from "zod";
|
|
3976
3101
|
|
|
3977
3102
|
// src/lib/worktree.ts
|
|
3978
|
-
import { execFile } from "node:child_process";
|
|
3979
|
-
import {
|
|
3980
|
-
import { copyFile, mkdir as mkdir6, readdir as readdir4, readFile as readFile8, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3103
|
+
import { execFile, spawn as spawn3 } from "node:child_process";
|
|
3104
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile8, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3981
3105
|
import {
|
|
3982
3106
|
basename as basename2,
|
|
3983
3107
|
dirname as dirname7,
|
|
3984
3108
|
isAbsolute as isAbsolute2,
|
|
3985
|
-
join as
|
|
3109
|
+
join as join10,
|
|
3986
3110
|
relative as relative2,
|
|
3987
3111
|
resolve as resolve3
|
|
3988
3112
|
} from "node:path";
|
|
@@ -4016,8 +3140,8 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
4016
3140
|
return out.trim().length > 0;
|
|
4017
3141
|
}
|
|
4018
3142
|
async function pruneOldWorktrees(repoRoot) {
|
|
4019
|
-
const dir =
|
|
4020
|
-
const stale = (await
|
|
3143
|
+
const dir = join10(stateDir(repoRoot), "worktrees");
|
|
3144
|
+
const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
4021
3145
|
for (const slug of stale) {
|
|
4022
3146
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
4023
3147
|
try {
|
|
@@ -4027,7 +3151,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
4027
3151
|
"worktree",
|
|
4028
3152
|
"remove",
|
|
4029
3153
|
"--force",
|
|
4030
|
-
|
|
3154
|
+
join10(dir, slug)
|
|
4031
3155
|
]);
|
|
4032
3156
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
4033
3157
|
} catch (err) {
|
|
@@ -4041,55 +3165,43 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
4041
3165
|
async function createWorktree(repoRoot) {
|
|
4042
3166
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
4043
3167
|
const dirSlug = branch.replace(/\//g, "-");
|
|
4044
|
-
const path =
|
|
3168
|
+
const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
|
|
4045
3169
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
4046
3170
|
await pruneOldWorktrees(repoRoot);
|
|
4047
3171
|
await mkdir6(dirname7(path), { recursive: true });
|
|
4048
3172
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
4049
3173
|
return { path, branch };
|
|
4050
3174
|
}
|
|
4051
|
-
async function
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
return { ok: code === 0, output: output.trim() };
|
|
4057
|
-
}
|
|
4058
|
-
async function installWorktreeDeps(worktreePath, toolchain) {
|
|
4059
|
-
const { profile: profile2, installSteps, packageManager } = toolchain;
|
|
4060
|
-
const declared = packageManager.dependency.mode === "agent-declares" ? packageManager.dependency.file : void 0;
|
|
4061
|
-
const haveSomethingToInstall = await hasProfileManifest(worktreePath, profile2) || declared !== void 0 && existsSync4(join12(worktreePath, declared));
|
|
4062
|
-
if (!haveSomethingToInstall) {
|
|
4063
|
-
return {
|
|
4064
|
-
ok: true,
|
|
4065
|
-
output: `no ${profile2.displayName} manifest; skipped install`
|
|
4066
|
-
};
|
|
4067
|
-
}
|
|
4068
|
-
if (installSteps.length === 0) {
|
|
4069
|
-
return {
|
|
4070
|
-
ok: true,
|
|
4071
|
-
output: `${profile2.displayName} (${toolchain.packageManager.id}) has no wizard-run install step`
|
|
4072
|
-
};
|
|
4073
|
-
}
|
|
4074
|
-
const outputs = [];
|
|
4075
|
-
for (const step of installSteps) {
|
|
4076
|
-
if (step.requiresFile && !existsSync4(join12(worktreePath, step.requiresFile)))
|
|
4077
|
-
continue;
|
|
4078
|
-
const result = await spawnStep(worktreePath, step.argv);
|
|
4079
|
-
if (result.output) outputs.push(result.output);
|
|
4080
|
-
if (result.ok) continue;
|
|
4081
|
-
if (step.optional) {
|
|
4082
|
-
logger.warn(
|
|
4083
|
-
{ step: step.argv.join(" "), output: result.output },
|
|
4084
|
-
"installWorktreeDeps: optional install step failed; continuing"
|
|
4085
|
-
);
|
|
4086
|
-
continue;
|
|
4087
|
-
}
|
|
4088
|
-
return { ok: false, output: outputs.join("\n").trim() };
|
|
3175
|
+
async function installWorktreeDeps(worktreePath) {
|
|
3176
|
+
try {
|
|
3177
|
+
await readPackageJson(worktreePath);
|
|
3178
|
+
} catch {
|
|
3179
|
+
return { ok: true, output: "no package.json; skipped install" };
|
|
4089
3180
|
}
|
|
4090
|
-
|
|
3181
|
+
const pm = await detectPackageManager(worktreePath);
|
|
3182
|
+
return new Promise((resolve4) => {
|
|
3183
|
+
let output = "";
|
|
3184
|
+
const child = spawn3(pm, ["install"], {
|
|
3185
|
+
cwd: worktreePath,
|
|
3186
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3187
|
+
});
|
|
3188
|
+
child.stdout?.on("data", (d) => output += d);
|
|
3189
|
+
child.stderr?.on("data", (d) => output += d);
|
|
3190
|
+
child.on(
|
|
3191
|
+
"error",
|
|
3192
|
+
(err) => resolve4({
|
|
3193
|
+
ok: false,
|
|
3194
|
+
output: `Failed to run ${pm} install: ${err.message}`
|
|
3195
|
+
})
|
|
3196
|
+
);
|
|
3197
|
+
child.on(
|
|
3198
|
+
"close",
|
|
3199
|
+
(code) => resolve4({ ok: code === 0, output: output.trim() })
|
|
3200
|
+
);
|
|
3201
|
+
});
|
|
4091
3202
|
}
|
|
4092
|
-
|
|
3203
|
+
var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
|
|
3204
|
+
function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
4093
3205
|
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
4094
3206
|
return {
|
|
4095
3207
|
ok: false,
|
|
@@ -4104,29 +3216,18 @@ function validateIngestEntrypoint(worktreePath, entrypoint, allowedExtensions) {
|
|
|
4104
3216
|
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
4105
3217
|
};
|
|
4106
3218
|
}
|
|
4107
|
-
if (allowedExtensions?.length && !allowedExtensions.some((ext) => entrypoint.endsWith(ext))) {
|
|
4108
|
-
return {
|
|
4109
|
-
ok: false,
|
|
4110
|
-
reason: `entrypoint "${entrypoint}" is not one of ${allowedExtensions.join(", ")}`
|
|
4111
|
-
};
|
|
4112
|
-
}
|
|
4113
3219
|
return { ok: true, target };
|
|
4114
3220
|
}
|
|
4115
|
-
async function runIngestScript(worktreePath,
|
|
4116
|
-
|
|
4117
|
-
if (ingest.kind !== "auto") {
|
|
3221
|
+
async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
3222
|
+
if (!INGEST_RUNTIMES.includes(runtime)) {
|
|
4118
3223
|
return {
|
|
4119
3224
|
ran: false,
|
|
4120
3225
|
ok: false,
|
|
4121
3226
|
output: "",
|
|
4122
|
-
reason:
|
|
3227
|
+
reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
|
|
4123
3228
|
};
|
|
4124
3229
|
}
|
|
4125
|
-
const validated = validateIngestEntrypoint(
|
|
4126
|
-
worktreePath,
|
|
4127
|
-
entrypoint,
|
|
4128
|
-
ingest.entrypointExtensions
|
|
4129
|
-
);
|
|
3230
|
+
const validated = validateIngestEntrypoint(worktreePath, entrypoint);
|
|
4130
3231
|
if (!validated.ok) {
|
|
4131
3232
|
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
4132
3233
|
}
|
|
@@ -4147,13 +3248,29 @@ async function runIngestScript(worktreePath, toolchain, entrypoint, env = {}) {
|
|
|
4147
3248
|
reason: `entrypoint "${entrypoint}" does not exist`
|
|
4148
3249
|
};
|
|
4149
3250
|
}
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
3251
|
+
return new Promise((resolveRun) => {
|
|
3252
|
+
let output = "";
|
|
3253
|
+
const child = spawn3(runtime, [entrypoint], {
|
|
3254
|
+
cwd: worktreePath,
|
|
3255
|
+
shell: false,
|
|
3256
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3257
|
+
env: { ...process.env, ...env }
|
|
3258
|
+
});
|
|
3259
|
+
child.stdout?.on("data", (d) => output += d);
|
|
3260
|
+
child.stderr?.on("data", (d) => output += d);
|
|
3261
|
+
child.on(
|
|
3262
|
+
"error",
|
|
3263
|
+
(err) => resolveRun({
|
|
3264
|
+
ran: true,
|
|
3265
|
+
ok: false,
|
|
3266
|
+
output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
|
|
3267
|
+
})
|
|
3268
|
+
);
|
|
3269
|
+
child.on(
|
|
3270
|
+
"close",
|
|
3271
|
+
(code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
|
|
3272
|
+
);
|
|
4155
3273
|
});
|
|
4156
|
-
return { ran: true, ok: code === 0, output: output.trim() };
|
|
4157
3274
|
}
|
|
4158
3275
|
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
4159
3276
|
const trimmed = sourcePath.trim();
|
|
@@ -4168,8 +3285,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
4168
3285
|
} catch {
|
|
4169
3286
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
4170
3287
|
}
|
|
4171
|
-
const relPath =
|
|
4172
|
-
const dest =
|
|
3288
|
+
const relPath = join10(ingestDir, basename2(source));
|
|
3289
|
+
const dest = join10(worktreePath, relPath);
|
|
4173
3290
|
try {
|
|
4174
3291
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
4175
3292
|
await copyFile(source, dest);
|
|
@@ -4185,7 +3302,7 @@ function hasEnvVar(content, name) {
|
|
|
4185
3302
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
4186
3303
|
}
|
|
4187
3304
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
4188
|
-
const target =
|
|
3305
|
+
const target = join10(worktreePath, ".env");
|
|
4189
3306
|
let existing = "";
|
|
4190
3307
|
try {
|
|
4191
3308
|
existing = await readFile8(target, "utf8");
|
|
@@ -4305,33 +3422,69 @@ async function resolveSearchOnlyKey(index) {
|
|
|
4305
3422
|
}
|
|
4306
3423
|
|
|
4307
3424
|
// src/lib/algoliaDocs.ts
|
|
4308
|
-
import { readFileSync, existsSync as
|
|
4309
|
-
import { dirname as dirname8, join as
|
|
3425
|
+
import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
|
|
3426
|
+
import { dirname as dirname8, join as join11 } from "node:path";
|
|
4310
3427
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4311
|
-
var DOCS_SUBPATH =
|
|
3428
|
+
var DOCS_SUBPATH = join11("docs", "algolia-sdk");
|
|
4312
3429
|
function findDocsDir() {
|
|
4313
3430
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
4314
3431
|
for (; ; ) {
|
|
4315
|
-
const candidate =
|
|
4316
|
-
if (
|
|
3432
|
+
const candidate = join11(dir, DOCS_SUBPATH);
|
|
3433
|
+
if (existsSync2(candidate)) return candidate;
|
|
4317
3434
|
const parent = dirname8(dir);
|
|
4318
3435
|
if (parent === dir) return void 0;
|
|
4319
3436
|
dir = parent;
|
|
4320
3437
|
}
|
|
4321
3438
|
}
|
|
4322
|
-
function
|
|
3439
|
+
function loadAlgoliaDoc(language) {
|
|
3440
|
+
const docsDir = findDocsDir();
|
|
3441
|
+
if (!docsDir) {
|
|
3442
|
+
logger.warn(
|
|
3443
|
+
"algoliaDocs: docs/algolia-sdk not found; skipping SDK reference"
|
|
3444
|
+
);
|
|
3445
|
+
return "";
|
|
3446
|
+
}
|
|
3447
|
+
const files = readdirSync(docsDir).filter((f) => f.includes(language));
|
|
3448
|
+
if (files.length === 0) {
|
|
3449
|
+
logger.warn(
|
|
3450
|
+
{ language },
|
|
3451
|
+
"algoliaDocs: no SDK reference found for language; skipping"
|
|
3452
|
+
);
|
|
3453
|
+
return "";
|
|
3454
|
+
}
|
|
3455
|
+
return readFileSync(join11(docsDir, files[0]), "utf8").trim();
|
|
3456
|
+
}
|
|
3457
|
+
function getNamedDoc(name, language) {
|
|
4323
3458
|
const docsDir = findDocsDir();
|
|
4324
3459
|
if (!docsDir) {
|
|
4325
3460
|
logger.warn("docs/algolia-sdk not found");
|
|
4326
3461
|
return "";
|
|
4327
3462
|
}
|
|
4328
|
-
const file =
|
|
4329
|
-
if (!
|
|
4330
|
-
logger.warn({ name,
|
|
3463
|
+
const file = join11(docsDir, `${name}-${language}.md`);
|
|
3464
|
+
if (!existsSync2(file)) {
|
|
3465
|
+
logger.warn({ name, language }, "named SDK reference not found");
|
|
4331
3466
|
return "";
|
|
4332
3467
|
}
|
|
4333
3468
|
return readFileSync(file, "utf8").trim();
|
|
4334
3469
|
}
|
|
3470
|
+
function getFrameworkSpecificDoc(frameworks) {
|
|
3471
|
+
const fw = frameworks.map((f) => f.toLowerCase());
|
|
3472
|
+
if (fw.includes("vue") || fw.includes("nuxt")) {
|
|
3473
|
+
return loadAlgoliaDoc("vue");
|
|
3474
|
+
}
|
|
3475
|
+
if (fw.includes("react") || fw.includes("next.js")) {
|
|
3476
|
+
return loadAlgoliaDoc("react");
|
|
3477
|
+
}
|
|
3478
|
+
if (fw.includes("angular")) {
|
|
3479
|
+
return loadAlgoliaDoc("angular");
|
|
3480
|
+
}
|
|
3481
|
+
return loadAlgoliaDoc("js");
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
// src/lib/shell.ts
|
|
3485
|
+
function shellQuote(value) {
|
|
3486
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
3487
|
+
}
|
|
4335
3488
|
|
|
4336
3489
|
// src/actions/implement.ts
|
|
4337
3490
|
var implementSchema = z24.object({
|
|
@@ -4366,11 +3519,12 @@ var implementSchema = z24.object({
|
|
|
4366
3519
|
});
|
|
4367
3520
|
var implementationOutputSchema = z24.object({
|
|
4368
3521
|
summary: z24.string(),
|
|
4369
|
-
// Ingestion only:
|
|
4370
|
-
//
|
|
4371
|
-
//
|
|
4372
|
-
//
|
|
4373
|
-
//
|
|
3522
|
+
// Ingestion only: how to run the generated script, as a structured pair the
|
|
3523
|
+
// wizard turns into an argv (`<runtime> <entrypoint>`) — never a free-form
|
|
3524
|
+
// command string. `runtime` is constrained to an allowlisted interpreter and
|
|
3525
|
+
// `entrypoint` is validated to a worktree-relative path before execution, so
|
|
3526
|
+
// the agent cannot inject extra commands or swap the interpreter.
|
|
3527
|
+
runtime: z24.enum(INGEST_RUNTIMES).optional(),
|
|
4374
3528
|
entrypoint: z24.string().optional()
|
|
4375
3529
|
});
|
|
4376
3530
|
var verificationOutputSchema = z24.object({
|
|
@@ -4380,11 +3534,47 @@ var verificationOutputSchema = z24.object({
|
|
|
4380
3534
|
});
|
|
4381
3535
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
4382
3536
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
3537
|
+
var INGEST_DIR = ".algolia-wizard";
|
|
3538
|
+
function detectUiFramework(language) {
|
|
3539
|
+
const names = language.frameworks.map((f) => f.name.toLowerCase());
|
|
3540
|
+
if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
|
|
3541
|
+
if (names.some((n) => n.includes("react") || n.includes("next")))
|
|
3542
|
+
return "React";
|
|
3543
|
+
if (names.some((n) => n.includes("angular"))) return "Angular";
|
|
3544
|
+
return "JavaScript";
|
|
3545
|
+
}
|
|
3546
|
+
function frameworksForDoc(framework) {
|
|
3547
|
+
switch (framework) {
|
|
3548
|
+
case "React":
|
|
3549
|
+
return ["react"];
|
|
3550
|
+
case "Vue":
|
|
3551
|
+
return ["vue"];
|
|
3552
|
+
case "Angular":
|
|
3553
|
+
return ["angular"];
|
|
3554
|
+
case "JavaScript":
|
|
3555
|
+
return [];
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
function publicEnvPrefix(language) {
|
|
3559
|
+
const frameworkNames = language.frameworks.map(
|
|
3560
|
+
(framework) => framework.name.toLowerCase()
|
|
4387
3561
|
);
|
|
3562
|
+
if (frameworkNames.some((name) => name.includes("next"))) {
|
|
3563
|
+
return "NEXT_PUBLIC_";
|
|
3564
|
+
}
|
|
3565
|
+
if (frameworkNames.some((name) => name.includes("nuxt"))) {
|
|
3566
|
+
return "NUXT_PUBLIC_";
|
|
3567
|
+
}
|
|
3568
|
+
if (frameworkNames.some((name) => name.includes("astro"))) {
|
|
3569
|
+
return "PUBLIC_";
|
|
3570
|
+
}
|
|
3571
|
+
if (frameworkNames.some((name) => name.includes("vite"))) {
|
|
3572
|
+
return "VITE_";
|
|
3573
|
+
}
|
|
3574
|
+
return "PUBLIC_";
|
|
3575
|
+
}
|
|
3576
|
+
function searchEnvVars(language, appId, searchKey) {
|
|
3577
|
+
const prefix = publicEnvPrefix(language);
|
|
4388
3578
|
return [
|
|
4389
3579
|
{
|
|
4390
3580
|
name: `${prefix}ALGOLIA_APP_ID`,
|
|
@@ -4396,38 +3586,6 @@ function buildSearchEnvVars(language, strategy, appId, searchKey) {
|
|
|
4396
3586
|
}
|
|
4397
3587
|
];
|
|
4398
3588
|
}
|
|
4399
|
-
async function resolveIngestionProfile(ctx, language, repoRoot) {
|
|
4400
|
-
const { candidates, confirmed: confirmed3, onDisk } = await pickIngestionCandidates(
|
|
4401
|
-
repoRoot,
|
|
4402
|
-
language.languages.map((l) => l.name)
|
|
4403
|
-
);
|
|
4404
|
-
if (candidates.length === 0) {
|
|
4405
|
-
const chosen = confirmed3[0] ?? onDisk[0] ?? LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID];
|
|
4406
|
-
logger.warn(
|
|
4407
|
-
{
|
|
4408
|
-
confirmed: language.languages.map((l) => l.name),
|
|
4409
|
-
onDisk: onDisk.map((p) => p.id),
|
|
4410
|
-
chosen: chosen.id
|
|
4411
|
-
},
|
|
4412
|
-
"implement: no confirmed language matched a manifest on disk; falling back"
|
|
4413
|
-
);
|
|
4414
|
-
return chosen;
|
|
4415
|
-
}
|
|
4416
|
-
if (candidates.length === 1) return candidates[0];
|
|
4417
|
-
const backends = candidates.filter(isBackendLanguage);
|
|
4418
|
-
if (backends.length === 1) return backends[0];
|
|
4419
|
-
if (backends.length === 0) return candidates[0];
|
|
4420
|
-
if (isBackendLanguage(candidates[0])) return candidates[0];
|
|
4421
|
-
const options = backends.map((p) => p.displayName);
|
|
4422
|
-
const selection = await ctx.requestUserInput({
|
|
4423
|
-
prompt: "Which language should the ingestion script use?",
|
|
4424
|
-
promptType: "multipleChoice",
|
|
4425
|
-
options,
|
|
4426
|
-
defaultSelectedIndex: 0
|
|
4427
|
-
});
|
|
4428
|
-
const picked = typeof selection === "string" ? backends.find((p) => p.displayName === selection) : void 0;
|
|
4429
|
-
return picked ?? backends[0];
|
|
4430
|
-
}
|
|
4431
3589
|
function baseInstructions(input) {
|
|
4432
3590
|
return [
|
|
4433
3591
|
`Target Algolia index: ${input.targetIndex}`,
|
|
@@ -4455,48 +3613,37 @@ function sourceSpecificInstructions(input) {
|
|
|
4455
3613
|
generated: [
|
|
4456
3614
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
4457
3615
|
"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.",
|
|
4458
|
-
"In the script, read and parse each returned
|
|
3616
|
+
"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.",
|
|
4459
3617
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
4460
3618
|
]
|
|
4461
3619
|
};
|
|
4462
3620
|
return byLine[input.ingestionSource];
|
|
4463
3621
|
}
|
|
4464
3622
|
function ingestionInstructions(input) {
|
|
4465
|
-
const { ingestionProfile: profile2, toolchain } = input;
|
|
4466
|
-
const { ingest } = toolchain;
|
|
4467
|
-
const extensions = ingest.entrypointExtensions.join(", ");
|
|
4468
|
-
const runInstruction = ingest.kind === "auto" ? `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile2.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard runs it with \`${describeIngestCommand(ingest, profile2.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. "${profile2.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard does NOT run ${profile2.displayName} ${toolchain.packageManager.id} projects itself \u2014 it tells the developer to run \`${describeIngestCommand(ingest, profile2.ingestEntrypointExample)}\`, so also add whatever build configuration that command needs.`;
|
|
4469
3623
|
return [
|
|
4470
3624
|
...input.confirmed && input.confirmed.length ? [
|
|
4471
|
-
`
|
|
3625
|
+
`Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
|
|
4472
3626
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
4473
|
-
`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.
|
|
4474
|
-
|
|
3627
|
+
`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.`,
|
|
3628
|
+
"Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
|
|
4475
3629
|
"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.",
|
|
4476
|
-
getNamedDoc("save-records",
|
|
4477
|
-
|
|
3630
|
+
getNamedDoc("save-records", "js"),
|
|
3631
|
+
'Add algoliasearch to package.json "dependencies" with a valid version range; the wizard installs the worktree deps after you finish.',
|
|
4478
3632
|
"The summary should be extremely concise.",
|
|
4479
|
-
|
|
3633
|
+
`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.`,
|
|
4480
3634
|
...sourceSpecificInstructions(input)
|
|
4481
3635
|
] : []
|
|
4482
3636
|
];
|
|
4483
3637
|
}
|
|
4484
3638
|
function searchInstructions(input) {
|
|
4485
|
-
const doc =
|
|
4486
|
-
"instantsearch-setup",
|
|
4487
|
-
searchDocKey(input.searchStrategy)
|
|
4488
|
-
);
|
|
4489
|
-
const isTemplate = input.searchStrategy === "cdn-template";
|
|
4490
|
-
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.`;
|
|
3639
|
+
const doc = getFrameworkSpecificDoc(frameworksForDoc(input.uiFramework));
|
|
4491
3640
|
return [
|
|
4492
3641
|
"Implement an in-app Algolia search experience.",
|
|
4493
|
-
`Build the search UI for ${
|
|
4494
|
-
"Follow the Algolia reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3642
|
+
`Build the search UI for ${input.uiFramework}.`,
|
|
3643
|
+
"Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
4495
3644
|
doc,
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
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.',
|
|
4499
|
-
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.",
|
|
3645
|
+
`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.`,
|
|
3646
|
+
"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.",
|
|
4500
3647
|
// appId always resolves (loadActiveProfile throws otherwise); only the
|
|
4501
3648
|
// search-only key is best-effort and can fall back to a placeholder.
|
|
4502
3649
|
`Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
@@ -4504,22 +3651,20 @@ function searchInstructions(input) {
|
|
|
4504
3651
|
// resolved app id / search-only key into ".env" under these exact names
|
|
4505
3652
|
// right after this step, so a renamed prefix here would leave the code
|
|
4506
3653
|
// reading a var the wizard never wrote.
|
|
4507
|
-
`Use exactly these env var names: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3654
|
+
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3655
|
+
'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.',
|
|
4508
3656
|
"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."
|
|
4509
3657
|
];
|
|
4510
3658
|
}
|
|
4511
3659
|
function verificationInstructions(input) {
|
|
4512
|
-
const protectedDirs = [
|
|
4513
|
-
.../* @__PURE__ */ new Set([input.ingestDir, ingestScriptDir(input.ingestionProfile)])
|
|
4514
|
-
];
|
|
4515
3660
|
return [
|
|
4516
3661
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
4517
3662
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
4518
|
-
|
|
3663
|
+
"Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
|
|
4519
3664
|
"For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
|
|
4520
3665
|
"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.",
|
|
4521
3666
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
4522
|
-
`Do not modify ${
|
|
3667
|
+
`Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
|
|
4523
3668
|
"Always call reportStatus with status=success once verification has run, even when sufficient=false.",
|
|
4524
3669
|
"Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
|
|
4525
3670
|
"Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
|
|
@@ -4528,17 +3673,14 @@ function verificationInstructions(input) {
|
|
|
4528
3673
|
var IMPLEMENT_CONFIG = {
|
|
4529
3674
|
ingestion: {
|
|
4530
3675
|
title: "Algolia ingestion",
|
|
4531
|
-
label: "Ingestion",
|
|
4532
3676
|
buildInstructions: ingestionInstructions
|
|
4533
3677
|
},
|
|
4534
3678
|
search: {
|
|
4535
3679
|
title: "Algolia search",
|
|
4536
|
-
label: "Search",
|
|
4537
3680
|
buildInstructions: searchInstructions
|
|
4538
3681
|
},
|
|
4539
3682
|
verification: {
|
|
4540
3683
|
title: "Algolia verification",
|
|
4541
|
-
label: "Verification",
|
|
4542
3684
|
buildInstructions: verificationInstructions
|
|
4543
3685
|
}
|
|
4544
3686
|
};
|
|
@@ -4570,10 +3712,11 @@ function buildAgentInstructions(useCase, input, extraInstructions = []) {
|
|
|
4570
3712
|
];
|
|
4571
3713
|
}
|
|
4572
3714
|
function formatSummary(useCase, summary) {
|
|
4573
|
-
|
|
3715
|
+
const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
|
|
3716
|
+
return `${label}: ${summary}`;
|
|
4574
3717
|
}
|
|
4575
|
-
function buildIngestCommand(worktree,
|
|
4576
|
-
return `cd ${shellQuote(worktree)} && ${
|
|
3718
|
+
function buildIngestCommand(worktree, runtime, entrypoint) {
|
|
3719
|
+
return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
|
|
4577
3720
|
}
|
|
4578
3721
|
function parseIngestRecordCount(output) {
|
|
4579
3722
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
@@ -4655,7 +3798,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4655
3798
|
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
4656
3799
|
}
|
|
4657
3800
|
const normalized = normalizeFindingPaths(findings);
|
|
4658
|
-
const
|
|
3801
|
+
const confirmed2 = normalized.confirmedEntities;
|
|
4659
3802
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
4660
3803
|
let appId;
|
|
4661
3804
|
let searchKey;
|
|
@@ -4693,66 +3836,31 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4693
3836
|
);
|
|
4694
3837
|
}
|
|
4695
3838
|
}
|
|
4696
|
-
const ingestionProfile = await resolveIngestionProfile(
|
|
4697
|
-
ctx,
|
|
4698
|
-
language,
|
|
4699
|
-
worktree
|
|
4700
|
-
);
|
|
4701
|
-
const toolchain = await resolveToolchain(worktree, ingestionProfile);
|
|
4702
|
-
const verificationLanguages = [
|
|
4703
|
-
.../* @__PURE__ */ new Set([
|
|
4704
|
-
ingestionProfile.id,
|
|
4705
|
-
...(await detectProfilesFromManifests(worktree)).map((p) => p.id)
|
|
4706
|
-
])
|
|
4707
|
-
];
|
|
4708
|
-
const frameworkName = language.frameworks[0]?.name;
|
|
4709
|
-
const searchStrategy = resolveSearchStrategy(
|
|
4710
|
-
frameworkName,
|
|
4711
|
-
verificationLanguages.includes(JAVASCRIPT)
|
|
4712
|
-
);
|
|
4713
|
-
logger.info(
|
|
4714
|
-
{
|
|
4715
|
-
language: ingestionProfile.id,
|
|
4716
|
-
packageManager: toolchain.packageManager.id,
|
|
4717
|
-
ingest: toolchain.ingest.kind,
|
|
4718
|
-
framework: frameworkName,
|
|
4719
|
-
searchStrategy
|
|
4720
|
-
},
|
|
4721
|
-
"implement: resolved ingestion toolchain and search strategy"
|
|
4722
|
-
);
|
|
4723
3839
|
const input = {
|
|
4724
3840
|
findings: normalized,
|
|
4725
|
-
confirmed:
|
|
3841
|
+
confirmed: confirmed2,
|
|
4726
3842
|
searchLocation,
|
|
4727
3843
|
targetIndex,
|
|
4728
3844
|
language,
|
|
4729
3845
|
appId,
|
|
4730
3846
|
searchKey,
|
|
4731
|
-
searchEnvVars:
|
|
4732
|
-
language,
|
|
4733
|
-
searchStrategy,
|
|
4734
|
-
appId,
|
|
4735
|
-
searchKey
|
|
4736
|
-
),
|
|
3847
|
+
searchEnvVars: searchEnvVars(language, appId, searchKey),
|
|
4737
3848
|
ingestDir: INGEST_DIR,
|
|
4738
3849
|
ingestionSource,
|
|
4739
3850
|
uploadFilePath,
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
toolchain,
|
|
4744
|
-
verificationLanguages
|
|
3851
|
+
// language.frameworks already prefers the confirm-framework step output,
|
|
3852
|
+
// so the user's confirmed stack (not just raw detection) picks the flavor.
|
|
3853
|
+
uiFramework: detectUiFramework(language)
|
|
4745
3854
|
};
|
|
4746
|
-
const searchToolchain = !bundlesJavaScript(searchStrategy) ? void 0 : ingestionProfile.id === JAVASCRIPT ? toolchain : await resolveToolchain(worktree, LANGUAGE_PROFILES[JAVASCRIPT]);
|
|
4747
|
-
const toolchainForUseCase = (useCase) => useCase === "search" ? searchToolchain : toolchain;
|
|
4748
3855
|
const summaries = [];
|
|
4749
3856
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
4750
3857
|
let agentRuns = 0;
|
|
3858
|
+
let ingestRuntime;
|
|
4751
3859
|
let ingestEntrypoint;
|
|
4752
3860
|
let ingestScriptRan = false;
|
|
4753
3861
|
let ingestRecordCount;
|
|
4754
3862
|
let ingestDurationMs;
|
|
4755
|
-
|
|
3863
|
+
let installFailed = false;
|
|
4756
3864
|
let ingestOutcomeMessage;
|
|
4757
3865
|
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
4758
3866
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
@@ -4766,19 +3874,16 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4766
3874
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
4767
3875
|
outputSchema: implementationOutputSchema
|
|
4768
3876
|
});
|
|
4769
|
-
const useCaseToolchain = toolchainForUseCase(currentUseCase);
|
|
4770
|
-
if (!useCaseToolchain) return result;
|
|
4771
3877
|
ctx.notify({
|
|
4772
3878
|
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
4773
3879
|
});
|
|
4774
3880
|
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
4775
|
-
useCase: currentUseCase
|
|
4776
|
-
language: useCaseToolchain.profile.id
|
|
3881
|
+
useCase: currentUseCase
|
|
4777
3882
|
});
|
|
4778
|
-
const install = await installWorktreeDeps(worktree
|
|
3883
|
+
const install = await installWorktreeDeps(worktree);
|
|
4779
3884
|
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
4780
3885
|
if (!install.ok) {
|
|
4781
|
-
|
|
3886
|
+
installFailed = true;
|
|
4782
3887
|
logger.warn(
|
|
4783
3888
|
{ useCase: currentUseCase, output: install.output },
|
|
4784
3889
|
"implement: dependency install in worktree failed; generated commands may not run until deps are installed"
|
|
@@ -4792,16 +3897,15 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4792
3897
|
return runAgent({
|
|
4793
3898
|
instructions: buildAgentInstructions("verification", input),
|
|
4794
3899
|
tools: toolsForUseCase("verification"),
|
|
4795
|
-
outputSchema: verificationOutputSchema
|
|
4796
|
-
// So verifyImplementation runs this repo's checks, not just npm scripts.
|
|
4797
|
-
languages: input.verificationLanguages
|
|
3900
|
+
outputSchema: verificationOutputSchema
|
|
4798
3901
|
});
|
|
4799
3902
|
}
|
|
4800
3903
|
if (useCases.includes("ingestion")) {
|
|
4801
|
-
const { summary, entrypoint } = await runImplementationUseCase("ingestion");
|
|
3904
|
+
const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
|
|
4802
3905
|
summaries.push(formatSummary("ingestion", summary));
|
|
3906
|
+
ingestRuntime = runtime;
|
|
4803
3907
|
ingestEntrypoint = entrypoint;
|
|
4804
|
-
if (
|
|
3908
|
+
if (ingestRuntime && ingestEntrypoint && !installFailed) {
|
|
4805
3909
|
ctx.clearNotices();
|
|
4806
3910
|
const runNow = await ctx.requestUserInput({
|
|
4807
3911
|
prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
|
|
@@ -4813,13 +3917,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4813
3917
|
const profile2 = await loadActiveProfile();
|
|
4814
3918
|
ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
|
|
4815
3919
|
const scriptLogId = ctx.logStart("runIngestScript", {
|
|
4816
|
-
|
|
3920
|
+
runtime: ingestRuntime,
|
|
4817
3921
|
entrypoint: ingestEntrypoint
|
|
4818
3922
|
});
|
|
4819
3923
|
const startedAt = Date.now();
|
|
4820
3924
|
const run = await runIngestScript(
|
|
4821
3925
|
worktree,
|
|
4822
|
-
|
|
3926
|
+
ingestRuntime,
|
|
4823
3927
|
ingestEntrypoint,
|
|
4824
3928
|
{
|
|
4825
3929
|
[APP_ID_VAR]: profile2.appId,
|
|
@@ -4833,7 +3937,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4833
3937
|
ingestRecordCount = parseIngestRecordCount(run.output);
|
|
4834
3938
|
if (ingestRecordCount != null) {
|
|
4835
3939
|
track("AI Wizard Ingest Successful", {
|
|
4836
|
-
entity_name:
|
|
3940
|
+
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
4837
3941
|
record_count: ingestRecordCount,
|
|
4838
3942
|
duration_ms: ingestDurationMs
|
|
4839
3943
|
});
|
|
@@ -4846,7 +3950,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4846
3950
|
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run.reason}`;
|
|
4847
3951
|
logger.warn(
|
|
4848
3952
|
{
|
|
4849
|
-
|
|
3953
|
+
runtime: ingestRuntime,
|
|
4850
3954
|
entrypoint: ingestEntrypoint,
|
|
4851
3955
|
reason: run.reason
|
|
4852
3956
|
},
|
|
@@ -4869,7 +3973,7 @@ ${run.output}` : status;
|
|
|
4869
3973
|
outcomeMessage = `\u274C Ingestion failed.${run.output ? ` ${run.output}` : ""}`;
|
|
4870
3974
|
logger.warn(
|
|
4871
3975
|
{
|
|
4872
|
-
|
|
3976
|
+
runtime: ingestRuntime,
|
|
4873
3977
|
entrypoint: ingestEntrypoint,
|
|
4874
3978
|
output: run.output
|
|
4875
3979
|
},
|
|
@@ -4886,15 +3990,10 @@ ${run.output}` : status;
|
|
|
4886
3990
|
}
|
|
4887
3991
|
}
|
|
4888
3992
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
4889
|
-
if (ingestEntrypoint) {
|
|
3993
|
+
if (ingestRuntime && ingestEntrypoint) {
|
|
4890
3994
|
commandMessages.push(
|
|
4891
|
-
`Ingestion command: ${buildIngestCommand(worktree,
|
|
3995
|
+
`Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
|
|
4892
3996
|
);
|
|
4893
|
-
if (toolchain.ingest.kind === "manual") {
|
|
4894
|
-
commandMessages.push(
|
|
4895
|
-
`The wizard does not run ${ingestionProfile.displayName} ${toolchain.packageManager.id} projects \u2014 run the command above yourself to ingest.`
|
|
4896
|
-
);
|
|
4897
|
-
}
|
|
4898
3997
|
}
|
|
4899
3998
|
await ctx.requestUserInput({
|
|
4900
3999
|
// No question being asked here, just an acknowledgement — the
|
|
@@ -4905,20 +4004,7 @@ ${run.output}` : status;
|
|
|
4905
4004
|
messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
|
|
4906
4005
|
});
|
|
4907
4006
|
}
|
|
4908
|
-
|
|
4909
|
-
if (skipSearch) {
|
|
4910
|
-
const target = describeSearchTarget(
|
|
4911
|
-
input.searchStrategy,
|
|
4912
|
-
input.frameworkName
|
|
4913
|
-
);
|
|
4914
|
-
summaries.push(
|
|
4915
|
-
`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).`
|
|
4916
|
-
);
|
|
4917
|
-
track("AI Wizard Search UI Skipped", {
|
|
4918
|
-
framework: input.frameworkName ?? "unknown"
|
|
4919
|
-
});
|
|
4920
|
-
}
|
|
4921
|
-
if (useCases.includes("search") && !skipSearch) {
|
|
4007
|
+
if (useCases.includes("search")) {
|
|
4922
4008
|
let extraInstructions = [];
|
|
4923
4009
|
const preSearchFiles = new Set(await listChangedFiles(worktree));
|
|
4924
4010
|
for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
|
|
@@ -4989,9 +4075,9 @@ ${run.output}` : status;
|
|
|
4989
4075
|
"implement: agent reported success but no files changed in the worktree"
|
|
4990
4076
|
);
|
|
4991
4077
|
}
|
|
4992
|
-
if (
|
|
4078
|
+
if (installFailed) {
|
|
4993
4079
|
summaries.push(
|
|
4994
|
-
|
|
4080
|
+
'\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".'
|
|
4995
4081
|
);
|
|
4996
4082
|
}
|
|
4997
4083
|
return {
|
|
@@ -4999,10 +4085,10 @@ ${run.output}` : status;
|
|
|
4999
4085
|
filesChanged,
|
|
5000
4086
|
summary: summaries.join("\n\n"),
|
|
5001
4087
|
worktreePath: worktree,
|
|
5002
|
-
...useCases.includes("ingestion") && ingestEntrypoint ? {
|
|
4088
|
+
...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
|
|
5003
4089
|
ingestCommand: buildIngestCommand(
|
|
5004
4090
|
worktree,
|
|
5005
|
-
|
|
4091
|
+
ingestRuntime,
|
|
5006
4092
|
ingestEntrypoint
|
|
5007
4093
|
),
|
|
5008
4094
|
ingestScriptRan,
|