@algolia/wizard 0.5.0 → 0.6.0-rc.51.23
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 +1123 -275
- package/docs/algolia-sdk/README.md +50 -22
- package/docs/algolia-sdk/instantsearch-setup-templates.md +92 -0
- package/docs/algolia-sdk/save-records-csharp.md +71 -0
- package/docs/algolia-sdk/save-records-dart.md +74 -0
- package/docs/algolia-sdk/save-records-go.md +62 -0
- package/docs/algolia-sdk/save-records-java.md +66 -0
- package/docs/algolia-sdk/save-records-kotlin.md +60 -0
- package/docs/algolia-sdk/save-records-php.md +50 -0
- package/docs/algolia-sdk/save-records-python.md +51 -0
- package/docs/algolia-sdk/save-records-ruby.md +48 -0
- package/docs/algolia-sdk/save-records-scala.md +68 -0
- package/docs/algolia-sdk/save-records-swift.md +88 -0
- package/package.json +1 -1
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 stack",
|
|
800
|
+
description: "React, Vue, Angular, Rails, Django, Laravel & more"
|
|
801
801
|
},
|
|
802
802
|
{
|
|
803
803
|
title: "scaffold a search UI",
|
|
804
|
-
description: "a styled InstantSearch
|
|
804
|
+
description: "a styled InstantSearch UI, wired into your app or templates"
|
|
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 package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
910
|
+
description: "reads your dependency manifests (package.json, Gemfile, go.mod, pom.xml\u2026), configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
911
911
|
},
|
|
912
912
|
{
|
|
913
913
|
tag: "WRITE",
|
|
@@ -2155,15 +2155,649 @@ 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
|
|
2158
|
+
import { readdir as readdir3, readFile as readFile7 } from "node:fs/promises";
|
|
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";
|
|
2159
2169
|
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 PY_VENV = `${INGEST_DIR}/.venv`;
|
|
2204
|
+
var PY_VENV_PYTHON = `${PY_VENV}/bin/python`;
|
|
2205
|
+
var PY_REQUIREMENTS = `${INGEST_DIR}/requirements.txt`;
|
|
2206
|
+
var CSHARP_PROJECT = `${INGEST_DIR}/ingest/ingest.csproj`;
|
|
2207
|
+
var SWIFT_PACKAGE_DIR = `${INGEST_DIR}/Ingest`;
|
|
2208
|
+
var LANGUAGE_PROFILES = {
|
|
2209
|
+
javascript: {
|
|
2210
|
+
id: "javascript",
|
|
2211
|
+
displayName: "JavaScript/TypeScript",
|
|
2212
|
+
aliases: [
|
|
2213
|
+
"javascript",
|
|
2214
|
+
"js",
|
|
2215
|
+
"typescript",
|
|
2216
|
+
"ts",
|
|
2217
|
+
"node",
|
|
2218
|
+
"nodejs",
|
|
2219
|
+
"node.js",
|
|
2220
|
+
"bun",
|
|
2221
|
+
"deno",
|
|
2222
|
+
"ecmascript",
|
|
2223
|
+
"jsx",
|
|
2224
|
+
"tsx"
|
|
2225
|
+
],
|
|
2226
|
+
manifests: ["package.json"],
|
|
2227
|
+
// The concrete npm-family manager is resolved by detectPackageManager (it
|
|
2228
|
+
// honours the package.json `packageManager` field, which lockfiles can't
|
|
2229
|
+
// express), so one spec covers all four and `resolveToolchain` rewrites the
|
|
2230
|
+
// binary below.
|
|
2231
|
+
packageManagers: [
|
|
2232
|
+
{
|
|
2233
|
+
id: "npm",
|
|
2234
|
+
dependency: { mode: "agent-declares", file: "package.json" },
|
|
2235
|
+
installSteps: [{ argv: ["npm", "install"] }],
|
|
2236
|
+
ingest: {
|
|
2237
|
+
kind: "auto",
|
|
2238
|
+
argv: ["node", ENTRYPOINT_TOKEN],
|
|
2239
|
+
entrypointExtensions: [".mjs", ".cjs", ".js"]
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
],
|
|
2243
|
+
sdk: { packageName: "algoliasearch", versionPin: "^5", docKey: "js" },
|
|
2244
|
+
ingestEntrypointExample: `${INGEST_DIR}/ingest.mjs`,
|
|
2245
|
+
// package.json scripts are repo-defined, so they're resolved at run time by
|
|
2246
|
+
// repoVerification rather than listed here.
|
|
2247
|
+
verification: [],
|
|
2248
|
+
envReadInstruction: "Read them from `process.env`.",
|
|
2249
|
+
skipDirs: ["node_modules", "dist", "build", "coverage", ".next", "out"]
|
|
2250
|
+
},
|
|
2251
|
+
python: {
|
|
2252
|
+
id: "python",
|
|
2253
|
+
displayName: "Python",
|
|
2254
|
+
aliases: ["python", "python3", "py", "cpython"],
|
|
2255
|
+
manifests: [
|
|
2256
|
+
"pyproject.toml",
|
|
2257
|
+
"requirements.txt",
|
|
2258
|
+
"setup.py",
|
|
2259
|
+
"setup.cfg",
|
|
2260
|
+
"Pipfile"
|
|
2261
|
+
],
|
|
2262
|
+
// Deliberately one path for every Python repo: a wizard-owned venv under
|
|
2263
|
+
// .algolia-wizard. Reusing the project's uv/poetry environment would mean
|
|
2264
|
+
// mutating the developer's real dependency manifest and lockfile, and the
|
|
2265
|
+
// declare-here/install-there split is the main way ingestion silently ends
|
|
2266
|
+
// up without the SDK installed. The tradeoff: the script can import the
|
|
2267
|
+
// Algolia client and anything it declares itself, but not the project's own
|
|
2268
|
+
// packages (see the optional root-requirements step below).
|
|
2269
|
+
packageManagers: [
|
|
2270
|
+
{
|
|
2271
|
+
id: "pip-venv",
|
|
2272
|
+
dependency: { mode: "agent-declares", file: PY_REQUIREMENTS },
|
|
2273
|
+
installSteps: [
|
|
2274
|
+
{ argv: ["python3", "-m", "venv", PY_VENV] },
|
|
2275
|
+
{
|
|
2276
|
+
argv: [PY_VENV_PYTHON, "-m", "pip", "install", "-r", PY_REQUIREMENTS]
|
|
2277
|
+
},
|
|
2278
|
+
// Best-effort access to the project's own dependencies (DB drivers,
|
|
2279
|
+
// ORMs) when the repo pins them the classic way.
|
|
2280
|
+
{
|
|
2281
|
+
argv: [
|
|
2282
|
+
PY_VENV_PYTHON,
|
|
2283
|
+
"-m",
|
|
2284
|
+
"pip",
|
|
2285
|
+
"install",
|
|
2286
|
+
"-r",
|
|
2287
|
+
"requirements.txt"
|
|
2288
|
+
],
|
|
2289
|
+
requiresFile: "requirements.txt",
|
|
2290
|
+
optional: true
|
|
2291
|
+
}
|
|
2292
|
+
],
|
|
2293
|
+
ingest: {
|
|
2294
|
+
kind: "auto",
|
|
2295
|
+
argv: [PY_VENV_PYTHON, ENTRYPOINT_TOKEN],
|
|
2296
|
+
entrypointExtensions: [".py"]
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
],
|
|
2300
|
+
sdk: {
|
|
2301
|
+
packageName: "algoliasearch",
|
|
2302
|
+
versionPin: ">=4,<5",
|
|
2303
|
+
docKey: "python"
|
|
2304
|
+
},
|
|
2305
|
+
ingestEntrypointExample: `${INGEST_DIR}/ingest.py`,
|
|
2306
|
+
verification: [
|
|
2307
|
+
{
|
|
2308
|
+
// -x skips the venv this same directory holds; without it the check
|
|
2309
|
+
// compiles every installed package instead of the generated script.
|
|
2310
|
+
label: "python compileall",
|
|
2311
|
+
argv: ["python3", "-m", "compileall", "-q", "-x", "[.]venv", INGEST_DIR]
|
|
2312
|
+
}
|
|
2313
|
+
],
|
|
2314
|
+
envReadInstruction: "Read them from `os.environ`.",
|
|
2315
|
+
skipDirs: [
|
|
2316
|
+
"venv",
|
|
2317
|
+
"__pycache__",
|
|
2318
|
+
"site-packages",
|
|
2319
|
+
"dist",
|
|
2320
|
+
"build",
|
|
2321
|
+
"htmlcov"
|
|
2322
|
+
]
|
|
2323
|
+
},
|
|
2324
|
+
ruby: {
|
|
2325
|
+
id: "ruby",
|
|
2326
|
+
displayName: "Ruby",
|
|
2327
|
+
aliases: ["ruby", "rb", "rails", "ruby on rails", "rubyonrails"],
|
|
2328
|
+
manifests: ["Gemfile", "*.gemspec"],
|
|
2329
|
+
packageManagers: [
|
|
2330
|
+
{
|
|
2331
|
+
id: "bundler",
|
|
2332
|
+
dependency: { mode: "agent-declares", file: "Gemfile" },
|
|
2333
|
+
installSteps: [{ argv: ["bundle", "install"] }],
|
|
2334
|
+
ingest: {
|
|
2335
|
+
kind: "auto",
|
|
2336
|
+
argv: ["bundle", "exec", "ruby", ENTRYPOINT_TOKEN],
|
|
2337
|
+
entrypointExtensions: [".rb"]
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
],
|
|
2341
|
+
sdk: { packageName: "algolia", versionPin: "~> 3.0", docKey: "ruby" },
|
|
2342
|
+
ingestEntrypointExample: `${INGEST_DIR}/ingest.rb`,
|
|
2343
|
+
// Ruby has no directory-level syntax check (`ruby -c` is one file at a
|
|
2344
|
+
// time), so verification relies on the agent's own review here.
|
|
2345
|
+
verification: [],
|
|
2346
|
+
envReadInstruction: "Read them from `ENV.fetch('NAME')`.",
|
|
2347
|
+
skipDirs: ["vendor", "tmp", "log", "coverage"]
|
|
2348
|
+
},
|
|
2349
|
+
php: {
|
|
2350
|
+
id: "php",
|
|
2351
|
+
displayName: "PHP",
|
|
2352
|
+
aliases: ["php", "laravel", "symfony"],
|
|
2353
|
+
manifests: ["composer.json"],
|
|
2354
|
+
packageManagers: [
|
|
2355
|
+
{
|
|
2356
|
+
id: "composer",
|
|
2357
|
+
// `composer require` both declares and installs, and unlike editing
|
|
2358
|
+
// composer.json by hand it can't leave composer.lock out of date (which
|
|
2359
|
+
// makes a later `composer install` refuse to run).
|
|
2360
|
+
dependency: { mode: "wizard-installs" },
|
|
2361
|
+
installSteps: [
|
|
2362
|
+
{
|
|
2363
|
+
argv: [
|
|
2364
|
+
"composer",
|
|
2365
|
+
"require",
|
|
2366
|
+
"algolia/algoliasearch-client-php:^4",
|
|
2367
|
+
"--no-interaction",
|
|
2368
|
+
// Repo post-install scripts are the project's code, not ours to
|
|
2369
|
+
// trigger; Laravel's package:discover also fails in a bare tree.
|
|
2370
|
+
"--no-scripts"
|
|
2371
|
+
]
|
|
2372
|
+
}
|
|
2373
|
+
],
|
|
2374
|
+
ingest: {
|
|
2375
|
+
kind: "auto",
|
|
2376
|
+
argv: ["php", ENTRYPOINT_TOKEN],
|
|
2377
|
+
entrypointExtensions: [".php"]
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
],
|
|
2381
|
+
sdk: {
|
|
2382
|
+
packageName: "algolia/algoliasearch-client-php",
|
|
2383
|
+
versionPin: "^4",
|
|
2384
|
+
docKey: "php"
|
|
2385
|
+
},
|
|
2386
|
+
ingestEntrypointExample: `${INGEST_DIR}/ingest.php`,
|
|
2387
|
+
verification: [],
|
|
2388
|
+
envReadInstruction: "Read them from `getenv('NAME')`.",
|
|
2389
|
+
skipDirs: ["vendor", "node_modules"]
|
|
2390
|
+
},
|
|
2391
|
+
go: {
|
|
2392
|
+
id: "go",
|
|
2393
|
+
displayName: "Go",
|
|
2394
|
+
aliases: ["go", "golang"],
|
|
2395
|
+
manifests: ["go.mod"],
|
|
2396
|
+
packageManagers: [
|
|
2397
|
+
{
|
|
2398
|
+
id: "gomod",
|
|
2399
|
+
// Imports in the generated file are the declaration; `go mod tidy`
|
|
2400
|
+
// resolves and fetches them.
|
|
2401
|
+
dependency: { mode: "code-imports" },
|
|
2402
|
+
installSteps: [{ argv: ["go", "mod", "tidy"] }],
|
|
2403
|
+
ingest: {
|
|
2404
|
+
kind: "auto",
|
|
2405
|
+
argv: ["go", "run", ENTRYPOINT_TOKEN],
|
|
2406
|
+
entrypointExtensions: [".go"]
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
],
|
|
2410
|
+
sdk: {
|
|
2411
|
+
packageName: "github.com/algolia/algoliasearch-client-go/v4",
|
|
2412
|
+
versionPin: "v4",
|
|
2413
|
+
docKey: "go"
|
|
2414
|
+
},
|
|
2415
|
+
ingestEntrypointExample: `${INGEST_DIR}/ingest.go`,
|
|
2416
|
+
verification: [
|
|
2417
|
+
{ label: "go vet", argv: ["go", "vet", "./..."], requiresFile: "go.mod" }
|
|
2418
|
+
],
|
|
2419
|
+
envReadInstruction: "Read them from `os.Getenv`.",
|
|
2420
|
+
skipDirs: ["vendor", "bin"]
|
|
2421
|
+
},
|
|
2422
|
+
java: {
|
|
2423
|
+
id: "java",
|
|
2424
|
+
displayName: "Java",
|
|
2425
|
+
aliases: ["java"],
|
|
2426
|
+
manifests: ["pom.xml", "build.gradle", "build.gradle.kts"],
|
|
2427
|
+
packageManagers: [
|
|
2428
|
+
{
|
|
2429
|
+
id: "maven",
|
|
2430
|
+
detectFiles: ["pom.xml"],
|
|
2431
|
+
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2432
|
+
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2433
|
+
// The main class is a wizard constant the instructions require the agent
|
|
2434
|
+
// to use, so execution can't be redirected by agent output. Runnable only
|
|
2435
|
+
// because the install step above compiles src/main/java first — which is
|
|
2436
|
+
// why the entrypoint lives there rather than under .algolia-wizard/.
|
|
2437
|
+
ingest: {
|
|
2438
|
+
kind: "auto",
|
|
2439
|
+
argv: [
|
|
2440
|
+
"mvn",
|
|
2441
|
+
"-q",
|
|
2442
|
+
"org.codehaus.mojo:exec-maven-plugin:3.5.0:java",
|
|
2443
|
+
"-Dexec.mainClass=AlgoliaWizardIngest"
|
|
2444
|
+
],
|
|
2445
|
+
entrypointExtensions: [".java"]
|
|
2446
|
+
}
|
|
2447
|
+
},
|
|
2448
|
+
{
|
|
2449
|
+
id: "gradle",
|
|
2450
|
+
detectFiles: ["build.gradle", "build.gradle.kts"],
|
|
2451
|
+
dependency: { mode: "agent-declares", file: "build.gradle" },
|
|
2452
|
+
installSteps: [],
|
|
2453
|
+
// Auto-running means executing the repo's own ./gradlew wrapper; out of
|
|
2454
|
+
// scope for now, so the wizard writes the code and prints the command.
|
|
2455
|
+
ingest: {
|
|
2456
|
+
kind: "manual",
|
|
2457
|
+
entrypointExtensions: [".java"],
|
|
2458
|
+
runCommand: "./gradlew runAlgoliaIngest"
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
],
|
|
2462
|
+
sdk: {
|
|
2463
|
+
packageName: "com.algolia:algoliasearch",
|
|
2464
|
+
versionPin: "4.+",
|
|
2465
|
+
docKey: "java",
|
|
2466
|
+
alsoRequires: "The class must be named AlgoliaWizardIngest, in the default package (no `package` statement), with a `public static void main`."
|
|
2467
|
+
},
|
|
2468
|
+
// Not under .algolia-wizard/: Maven and Gradle only compile src/main/<lang>,
|
|
2469
|
+
// so a class outside it never makes it onto the classpath and the run command
|
|
2470
|
+
// fails with "class not found".
|
|
2471
|
+
ingestEntrypointExample: "src/main/java/AlgoliaWizardIngest.java",
|
|
2472
|
+
verification: [
|
|
2473
|
+
{
|
|
2474
|
+
label: "mvn compile",
|
|
2475
|
+
argv: ["mvn", "-q", "-DskipTests", "compile"],
|
|
2476
|
+
requiresFile: "pom.xml"
|
|
2477
|
+
}
|
|
2478
|
+
],
|
|
2479
|
+
envReadInstruction: "Read them from `System.getenv`.",
|
|
2480
|
+
skipDirs: ["target", "build", "out"]
|
|
2481
|
+
},
|
|
2482
|
+
kotlin: {
|
|
2483
|
+
id: "kotlin",
|
|
2484
|
+
displayName: "Kotlin",
|
|
2485
|
+
aliases: ["kotlin", "kt", "ktor"],
|
|
2486
|
+
manifests: ["build.gradle.kts", "build.gradle", "pom.xml"],
|
|
2487
|
+
packageManagers: [
|
|
2488
|
+
{
|
|
2489
|
+
id: "gradle",
|
|
2490
|
+
dependency: { mode: "agent-declares", file: "build.gradle.kts" },
|
|
2491
|
+
installSteps: [],
|
|
2492
|
+
ingest: {
|
|
2493
|
+
kind: "manual",
|
|
2494
|
+
entrypointExtensions: [".kt"],
|
|
2495
|
+
runCommand: "./gradlew runAlgoliaIngest"
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
],
|
|
2499
|
+
sdk: {
|
|
2500
|
+
packageName: "com.algolia:algoliasearch-client-kotlin",
|
|
2501
|
+
versionPin: "3.+",
|
|
2502
|
+
docKey: "kotlin",
|
|
2503
|
+
// The published client's commonMain ships only ktor-client-core; without an
|
|
2504
|
+
// engine the script compiles and then fails at its first request.
|
|
2505
|
+
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."
|
|
2506
|
+
},
|
|
2507
|
+
ingestEntrypointExample: "src/main/kotlin/AlgoliaWizardIngest.kt",
|
|
2508
|
+
verification: [],
|
|
2509
|
+
envReadInstruction: "Read them from `System.getenv`.",
|
|
2510
|
+
skipDirs: ["build", "out"]
|
|
2511
|
+
},
|
|
2512
|
+
scala: {
|
|
2513
|
+
id: "scala",
|
|
2514
|
+
displayName: "Scala",
|
|
2515
|
+
aliases: ["scala", "sbt"],
|
|
2516
|
+
manifests: ["build.sbt", "build.sc"],
|
|
2517
|
+
packageManagers: [
|
|
2518
|
+
{
|
|
2519
|
+
id: "sbt",
|
|
2520
|
+
dependency: { mode: "agent-declares", file: "build.sbt" },
|
|
2521
|
+
installSteps: [],
|
|
2522
|
+
ingest: {
|
|
2523
|
+
kind: "manual",
|
|
2524
|
+
entrypointExtensions: [".scala"],
|
|
2525
|
+
runCommand: 'sbt "runMain AlgoliaWizardIngest"'
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
],
|
|
2529
|
+
sdk: {
|
|
2530
|
+
packageName: "com.algolia:algoliasearch-scala_2.13",
|
|
2531
|
+
versionPin: "2.+",
|
|
2532
|
+
docKey: "scala",
|
|
2533
|
+
alsoRequires: "Name the object AlgoliaWizardIngest in the default package (no `package` statement) so `runMain AlgoliaWizardIngest` resolves it."
|
|
2534
|
+
},
|
|
2535
|
+
ingestEntrypointExample: "src/main/scala/AlgoliaWizardIngest.scala",
|
|
2536
|
+
verification: [],
|
|
2537
|
+
envReadInstruction: "Read them from `sys.env`.",
|
|
2538
|
+
// `project/` holds sbt's build definition, but the name is generic enough
|
|
2539
|
+
// that some repos use it for source; scanning it is cheap, missing source
|
|
2540
|
+
// is not.
|
|
2541
|
+
skipDirs: ["target"]
|
|
2542
|
+
},
|
|
2543
|
+
csharp: {
|
|
2544
|
+
id: "csharp",
|
|
2545
|
+
displayName: "C#",
|
|
2546
|
+
aliases: ["c#", "csharp", "cs", ".net", "dotnet", "net", "asp.net"],
|
|
2547
|
+
manifests: ["*.csproj", "*.sln", "global.json"],
|
|
2548
|
+
packageManagers: [
|
|
2549
|
+
{
|
|
2550
|
+
id: "dotnet",
|
|
2551
|
+
// A self-contained project under .algolia-wizard keeps the ingest script
|
|
2552
|
+
// out of the repo's own build graph.
|
|
2553
|
+
dependency: { mode: "agent-declares", file: CSHARP_PROJECT },
|
|
2554
|
+
installSteps: [{ argv: ["dotnet", "restore", CSHARP_PROJECT] }],
|
|
2555
|
+
ingest: {
|
|
2556
|
+
kind: "auto",
|
|
2557
|
+
argv: ["dotnet", "run", "--project", ENTRYPOINT_TOKEN],
|
|
2558
|
+
entrypointExtensions: [".csproj"]
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
],
|
|
2562
|
+
sdk: {
|
|
2563
|
+
packageName: "Algolia.Search",
|
|
2564
|
+
versionPin: "7.*",
|
|
2565
|
+
docKey: "csharp"
|
|
2566
|
+
},
|
|
2567
|
+
ingestEntrypointExample: CSHARP_PROJECT,
|
|
2568
|
+
verification: [
|
|
2569
|
+
{
|
|
2570
|
+
label: "dotnet build",
|
|
2571
|
+
argv: ["dotnet", "build", CSHARP_PROJECT, "--nologo"],
|
|
2572
|
+
requiresFile: CSHARP_PROJECT
|
|
2573
|
+
}
|
|
2574
|
+
],
|
|
2575
|
+
envReadInstruction: 'Read them from `Environment.GetEnvironmentVariable("NAME")`.',
|
|
2576
|
+
// Deliberately not `packages`: modern .NET uses PackageReference, and
|
|
2577
|
+
// `packages/` is where pnpm/Lerna/Turborepo monorepos keep all their source —
|
|
2578
|
+
// skipping it would hide the entities the scan is looking for.
|
|
2579
|
+
skipDirs: ["bin", "obj"]
|
|
2580
|
+
},
|
|
2581
|
+
swift: {
|
|
2582
|
+
id: "swift",
|
|
2583
|
+
displayName: "Swift",
|
|
2584
|
+
aliases: ["swift", "swiftui", "ios", "vapor"],
|
|
2585
|
+
manifests: ["Package.swift", "*.xcodeproj", "*.xcworkspace"],
|
|
2586
|
+
packageManagers: [
|
|
2587
|
+
{
|
|
2588
|
+
id: "swiftpm",
|
|
2589
|
+
dependency: {
|
|
2590
|
+
mode: "agent-declares",
|
|
2591
|
+
file: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2592
|
+
},
|
|
2593
|
+
// `swift build` resolves and fetches; a cold build of the client is slow
|
|
2594
|
+
// (minutes), which is why the caller degrades to the manual command when
|
|
2595
|
+
// this fails.
|
|
2596
|
+
installSteps: [
|
|
2597
|
+
{
|
|
2598
|
+
argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2599
|
+
requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2600
|
+
}
|
|
2601
|
+
],
|
|
2602
|
+
ingest: {
|
|
2603
|
+
kind: "auto",
|
|
2604
|
+
argv: ["swift", "run", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2605
|
+
entrypointExtensions: [".swift"]
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
],
|
|
2609
|
+
sdk: {
|
|
2610
|
+
packageName: "algoliasearch-client-swift",
|
|
2611
|
+
// SwiftPM range syntax, not an exact version — a bare "9.0.0" in a
|
|
2612
|
+
// Package.swift dependency pins the patch.
|
|
2613
|
+
versionPin: 'from: "9.0.0"',
|
|
2614
|
+
docKey: "swift"
|
|
2615
|
+
},
|
|
2616
|
+
ingestEntrypointExample: `${SWIFT_PACKAGE_DIR}/Sources/Ingest/main.swift`,
|
|
2617
|
+
verification: [
|
|
2618
|
+
{
|
|
2619
|
+
label: "swift build",
|
|
2620
|
+
argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2621
|
+
requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2622
|
+
}
|
|
2623
|
+
],
|
|
2624
|
+
envReadInstruction: "Read them from `ProcessInfo.processInfo.environment`.",
|
|
2625
|
+
skipDirs: ["Pods", "DerivedData", "Carthage", ".build"]
|
|
2626
|
+
},
|
|
2627
|
+
dart: {
|
|
2628
|
+
id: "dart",
|
|
2629
|
+
displayName: "Dart",
|
|
2630
|
+
aliases: ["dart", "flutter"],
|
|
2631
|
+
manifests: ["pubspec.yaml"],
|
|
2632
|
+
packageManagers: [
|
|
2633
|
+
{
|
|
2634
|
+
id: "flutter-pub",
|
|
2635
|
+
detectFiles: [".metadata"],
|
|
2636
|
+
dependency: { mode: "agent-declares", file: "pubspec.yaml" },
|
|
2637
|
+
installSteps: [{ argv: ["flutter", "pub", "get"] }],
|
|
2638
|
+
ingest: {
|
|
2639
|
+
kind: "auto",
|
|
2640
|
+
argv: ["dart", "run", ENTRYPOINT_TOKEN],
|
|
2641
|
+
entrypointExtensions: [".dart"]
|
|
2642
|
+
}
|
|
2643
|
+
},
|
|
2644
|
+
{
|
|
2645
|
+
id: "pub",
|
|
2646
|
+
dependency: { mode: "agent-declares", file: "pubspec.yaml" },
|
|
2647
|
+
installSteps: [{ argv: ["dart", "pub", "get"] }],
|
|
2648
|
+
ingest: {
|
|
2649
|
+
kind: "auto",
|
|
2650
|
+
argv: ["dart", "run", ENTRYPOINT_TOKEN],
|
|
2651
|
+
entrypointExtensions: [".dart"]
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
],
|
|
2655
|
+
sdk: {
|
|
2656
|
+
packageName: "algolia_client_search",
|
|
2657
|
+
versionPin: "^1.0.0",
|
|
2658
|
+
docKey: "dart"
|
|
2659
|
+
},
|
|
2660
|
+
ingestEntrypointExample: `${INGEST_DIR}/ingest.dart`,
|
|
2661
|
+
verification: [
|
|
2662
|
+
{
|
|
2663
|
+
label: "dart analyze",
|
|
2664
|
+
argv: ["dart", "analyze", INGEST_DIR],
|
|
2665
|
+
requiresFile: "pubspec.yaml"
|
|
2666
|
+
}
|
|
2667
|
+
],
|
|
2668
|
+
envReadInstruction: "Read them from `Platform.environment`.",
|
|
2669
|
+
skipDirs: ["build"]
|
|
2670
|
+
}
|
|
2671
|
+
};
|
|
2672
|
+
var DEFAULT_LANGUAGE_ID = "javascript";
|
|
2673
|
+
function normalizeLanguageName(name) {
|
|
2674
|
+
return name.toLowerCase().trim().replace(/[\s_-]+/g, "");
|
|
2675
|
+
}
|
|
2676
|
+
var ALIAS_TO_ID = /* @__PURE__ */ new Map();
|
|
2677
|
+
for (const profile2 of Object.values(LANGUAGE_PROFILES)) {
|
|
2678
|
+
for (const alias of [profile2.id, profile2.displayName, ...profile2.aliases]) {
|
|
2679
|
+
ALIAS_TO_ID.set(normalizeLanguageName(alias), profile2.id);
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
function resolveLanguageProfile(name) {
|
|
2683
|
+
const id = ALIAS_TO_ID.get(normalizeLanguageName(name));
|
|
2684
|
+
return id ? LANGUAGE_PROFILES[id] : void 0;
|
|
2685
|
+
}
|
|
2686
|
+
var BASE_SKIP_DIRS = ["node_modules", ".git", "dist"];
|
|
2687
|
+
var ALL_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2688
|
+
...BASE_SKIP_DIRS,
|
|
2689
|
+
...Object.values(LANGUAGE_PROFILES).flatMap((p) => p.skipDirs)
|
|
2690
|
+
]);
|
|
2691
|
+
var ALLOWED_BINARIES = new Set(
|
|
2692
|
+
Object.values(LANGUAGE_PROFILES).flatMap((profile2) => [
|
|
2693
|
+
...profile2.packageManagers.flatMap((pm) => [
|
|
2694
|
+
...pm.installSteps.map((s) => s.argv[0]),
|
|
2695
|
+
...pm.ingest.kind === "auto" ? [pm.ingest.argv[0]] : []
|
|
2696
|
+
]),
|
|
2697
|
+
...profile2.verification.map((v) => v.argv[0])
|
|
2698
|
+
])
|
|
2699
|
+
);
|
|
2700
|
+
var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
|
|
2701
|
+
function isWorktreeRelativeCommand(command) {
|
|
2702
|
+
return command.includes("/");
|
|
2703
|
+
}
|
|
2704
|
+
function withCommand(argv, command) {
|
|
2705
|
+
return [command, ...argv.slice(1)];
|
|
2706
|
+
}
|
|
2707
|
+
async function manifestPresent(root, manifest, listing) {
|
|
2708
|
+
if (!manifest.startsWith("*.")) return existsSync2(join9(root, manifest));
|
|
2709
|
+
if (!listing.entries) {
|
|
2710
|
+
const entries = await readdir2(root).catch(() => []);
|
|
2711
|
+
listing.entries = Array.isArray(entries) ? entries : [];
|
|
2712
|
+
}
|
|
2713
|
+
const suffix = manifest.slice(1);
|
|
2714
|
+
return listing.entries.some((e) => e.endsWith(suffix));
|
|
2715
|
+
}
|
|
2716
|
+
async function profileManifestPresent(root, profile2, listing) {
|
|
2717
|
+
for (const manifest of profile2.manifests) {
|
|
2718
|
+
if (await manifestPresent(root, manifest, listing)) return true;
|
|
2719
|
+
}
|
|
2720
|
+
return false;
|
|
2721
|
+
}
|
|
2722
|
+
async function detectProfilesFromManifests(root) {
|
|
2723
|
+
const listing = {};
|
|
2724
|
+
const found = [];
|
|
2725
|
+
for (const profile2 of Object.values(LANGUAGE_PROFILES)) {
|
|
2726
|
+
if (await profileManifestPresent(root, profile2, listing)) found.push(profile2);
|
|
2727
|
+
}
|
|
2728
|
+
return found;
|
|
2729
|
+
}
|
|
2730
|
+
async function hasProfileManifest(root, profile2) {
|
|
2731
|
+
return profileManifestPresent(root, profile2, {});
|
|
2732
|
+
}
|
|
2733
|
+
async function resolveToolchain(root, profile2) {
|
|
2734
|
+
const matched = profile2.packageManagers.find(
|
|
2735
|
+
(pm) => [...pm.lockfiles ?? [], ...pm.detectFiles ?? []].some(
|
|
2736
|
+
(f) => existsSync2(join9(root, f))
|
|
2737
|
+
)
|
|
2738
|
+
);
|
|
2739
|
+
const packageManager = matched ?? profile2.packageManagers[0];
|
|
2740
|
+
let { installSteps, ingest } = packageManager;
|
|
2741
|
+
installSteps = installSteps.map(
|
|
2742
|
+
(step) => isWorktreeRelativeCommand(step.argv[0]) ? { ...step, argv: withCommand(step.argv, join9(root, step.argv[0])) } : step
|
|
2743
|
+
);
|
|
2744
|
+
if (ingest.kind === "auto" && isWorktreeRelativeCommand(ingest.argv[0])) {
|
|
2745
|
+
ingest = {
|
|
2746
|
+
...ingest,
|
|
2747
|
+
argv: withCommand(ingest.argv, join9(root, ingest.argv[0]))
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
if (profile2.id === "javascript") {
|
|
2751
|
+
const pm = await detectPackageManager(root);
|
|
2752
|
+
if (JS_PACKAGE_MANAGERS.has(pm)) {
|
|
2753
|
+
installSteps = installSteps.map((step) => ({
|
|
2754
|
+
...step,
|
|
2755
|
+
argv: withCommand(step.argv, pm)
|
|
2756
|
+
}));
|
|
2757
|
+
if (pm === "bun" && ingest.kind === "auto") {
|
|
2758
|
+
ingest = { ...ingest, argv: withCommand(ingest.argv, "bun") };
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
return { profile: profile2, packageManager, installSteps, ingest };
|
|
2763
|
+
}
|
|
2764
|
+
function resolveIngestArgv(ingest, entrypoint) {
|
|
2765
|
+
if (ingest.kind !== "auto") {
|
|
2766
|
+
throw new Error("resolveIngestArgv called for a manual-run toolchain");
|
|
2767
|
+
}
|
|
2768
|
+
return ingest.argv.map(
|
|
2769
|
+
(part) => part === ENTRYPOINT_TOKEN ? entrypoint : part
|
|
2770
|
+
);
|
|
2771
|
+
}
|
|
2772
|
+
function describeIngestCommand(ingest, entrypoint) {
|
|
2773
|
+
if (ingest.kind !== "auto") return ingest.runCommand;
|
|
2774
|
+
return ingest.argv.map((part) => part === ENTRYPOINT_TOKEN ? shellQuote(entrypoint) : part).join(" ");
|
|
2775
|
+
}
|
|
2776
|
+
function ingestScriptDir(profile2) {
|
|
2777
|
+
const parts = profile2.ingestEntrypointExample.split("/");
|
|
2778
|
+
return parts.slice(0, -1).join("/") || ".";
|
|
2779
|
+
}
|
|
2780
|
+
function dependencyInstruction(toolchain) {
|
|
2781
|
+
const { profile: profile2, packageManager } = toolchain;
|
|
2782
|
+
const { packageName, versionPin } = profile2.sdk;
|
|
2783
|
+
const also = profile2.sdk.alsoRequires ? ` ${profile2.sdk.alsoRequires}` : "";
|
|
2784
|
+
switch (packageManager.dependency.mode) {
|
|
2785
|
+
case "wizard-installs":
|
|
2786
|
+
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}`;
|
|
2787
|
+
case "code-imports":
|
|
2788
|
+
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}`;
|
|
2789
|
+
case "agent-declares":
|
|
2790
|
+
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}`;
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
// src/lib/tools/searchFiles.ts
|
|
2160
2795
|
var MAX_QUERY_LENGTH = 1e3;
|
|
2161
2796
|
async function walkFiles(dir) {
|
|
2162
|
-
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
2163
2797
|
const out = [];
|
|
2164
|
-
for (const e of await
|
|
2165
|
-
if (e.name.startsWith(".") ||
|
|
2166
|
-
const full =
|
|
2798
|
+
for (const e of await readdir3(dir, { withFileTypes: true })) {
|
|
2799
|
+
if (e.name.startsWith(".") || ALL_SKIP_DIRS.has(e.name)) continue;
|
|
2800
|
+
const full = join10(dir, e.name);
|
|
2167
2801
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2168
2802
|
else if (e.isFile()) out.push(full);
|
|
2169
2803
|
}
|
|
@@ -2196,7 +2830,7 @@ function searchFilesTool(ctx) {
|
|
|
2196
2830
|
for (const file of await walkFiles(resolved.target)) {
|
|
2197
2831
|
let content;
|
|
2198
2832
|
try {
|
|
2199
|
-
content = await
|
|
2833
|
+
content = await readFile7(file, "utf8");
|
|
2200
2834
|
} catch {
|
|
2201
2835
|
continue;
|
|
2202
2836
|
}
|
|
@@ -2220,6 +2854,10 @@ function searchFilesTool(ctx) {
|
|
|
2220
2854
|
import { tool as tool8 } from "ai";
|
|
2221
2855
|
import z11 from "zod";
|
|
2222
2856
|
|
|
2857
|
+
// src/lib/tools/repoVerification.ts
|
|
2858
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
2859
|
+
import { join as join11 } from "node:path";
|
|
2860
|
+
|
|
2223
2861
|
// src/lib/tools/utils/runCommand.ts
|
|
2224
2862
|
import { spawn as spawn2 } from "node:child_process";
|
|
2225
2863
|
function runCommand(command, args, cwd) {
|
|
@@ -2239,69 +2877,89 @@ function runCommand(command, args, cwd) {
|
|
|
2239
2877
|
});
|
|
2240
2878
|
}
|
|
2241
2879
|
|
|
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
|
-
|
|
2271
2880
|
// src/lib/tools/repoVerification.ts
|
|
2272
2881
|
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
2273
|
-
async function
|
|
2882
|
+
async function runCheck(command, binary, args) {
|
|
2883
|
+
const { code, output } = await runCommand(binary, args);
|
|
2884
|
+
return { command, exitCode: code, ok: code === 0, output: output.trim() };
|
|
2885
|
+
}
|
|
2886
|
+
async function javascriptChecks() {
|
|
2274
2887
|
let pkg;
|
|
2275
2888
|
try {
|
|
2276
2889
|
pkg = await readPackageJson();
|
|
2277
2890
|
} catch (err) {
|
|
2278
|
-
|
|
2279
|
-
|
|
2891
|
+
return {
|
|
2892
|
+
limitation: `Could not read package.json to detect verification conventions: ${err.message}`
|
|
2893
|
+
};
|
|
2280
2894
|
}
|
|
2281
2895
|
const scripts = pkg.scripts ?? {};
|
|
2282
2896
|
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
2283
2897
|
if (present.length === 0) {
|
|
2284
|
-
|
|
2285
|
-
|
|
2898
|
+
return {
|
|
2899
|
+
limitation: `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`
|
|
2900
|
+
};
|
|
2286
2901
|
}
|
|
2287
2902
|
const pm = await detectPackageManager(process.cwd());
|
|
2288
2903
|
const checks = [];
|
|
2289
2904
|
for (const script of present) {
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2905
|
+
checks.push(
|
|
2906
|
+
await runCheck(`${pm} run ${script}`, pm, ["run", script])
|
|
2907
|
+
);
|
|
2293
2908
|
}
|
|
2294
|
-
return {
|
|
2909
|
+
return { checks };
|
|
2910
|
+
}
|
|
2911
|
+
async function runRepoVerificationCheck(languages = [DEFAULT_LANGUAGE_ID]) {
|
|
2912
|
+
const ids = [...new Set(languages)];
|
|
2913
|
+
if (ids.length === 0) ids.push(DEFAULT_LANGUAGE_ID);
|
|
2914
|
+
const checks = [];
|
|
2915
|
+
const limitations = [];
|
|
2916
|
+
for (const id of ids) {
|
|
2917
|
+
if (id === DEFAULT_LANGUAGE_ID) {
|
|
2918
|
+
const result = await javascriptChecks();
|
|
2919
|
+
if ("checks" in result) checks.push(...result.checks);
|
|
2920
|
+
else limitations.push(result.limitation);
|
|
2921
|
+
continue;
|
|
2922
|
+
}
|
|
2923
|
+
const profile2 = LANGUAGE_PROFILES[id];
|
|
2924
|
+
const runnable = profile2.verification.filter(
|
|
2925
|
+
(spec) => !spec.requiresFile || existsSync3(join11(process.cwd(), spec.requiresFile))
|
|
2926
|
+
);
|
|
2927
|
+
if (runnable.length === 0) {
|
|
2928
|
+
limitations.push(
|
|
2929
|
+
`No mechanical verification available for ${profile2.displayName} in this repo.`
|
|
2930
|
+
);
|
|
2931
|
+
continue;
|
|
2932
|
+
}
|
|
2933
|
+
for (const spec of runnable) {
|
|
2934
|
+
checks.push(
|
|
2935
|
+
await runCheck(spec.argv.join(" "), spec.argv[0], [
|
|
2936
|
+
...spec.argv.slice(1)
|
|
2937
|
+
])
|
|
2938
|
+
);
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
if (checks.length === 0) {
|
|
2942
|
+
return {
|
|
2943
|
+
ok: false,
|
|
2944
|
+
checks: [],
|
|
2945
|
+
limitation: limitations.join(" ") || "No verification checks available."
|
|
2946
|
+
};
|
|
2947
|
+
}
|
|
2948
|
+
return {
|
|
2949
|
+
ok: checks.every((c) => c.ok),
|
|
2950
|
+
checks,
|
|
2951
|
+
...limitations.length ? { limitation: limitations.join(" ") } : {}
|
|
2952
|
+
};
|
|
2295
2953
|
}
|
|
2296
2954
|
|
|
2297
2955
|
// src/lib/tools/verifyImplementation.ts
|
|
2298
|
-
function verifyImplementationTool() {
|
|
2956
|
+
function verifyImplementationTool(ctx) {
|
|
2299
2957
|
return tool8({
|
|
2300
|
-
description: "Run the repo's mechanical verification
|
|
2958
|
+
description: "Run the repo's mechanical verification checks for generated implementation changes. Uses the conventions of the repo's languages (package.json lint/typecheck/check scripts for JavaScript, the equivalent compile/analyze command elsewhere) and returns structured pass/fail evidence for the verifier to interpret.",
|
|
2301
2959
|
inputSchema: z11.object(),
|
|
2302
2960
|
execute: async () => {
|
|
2303
|
-
logger.info("called verifyImplementation tool");
|
|
2304
|
-
return runRepoVerificationCheck();
|
|
2961
|
+
logger.info({ languages: ctx.languages }, "called verifyImplementation tool");
|
|
2962
|
+
return runRepoVerificationCheck(ctx.languages);
|
|
2305
2963
|
}
|
|
2306
2964
|
});
|
|
2307
2965
|
}
|
|
@@ -2427,12 +3085,13 @@ var DEFAULT_TOOL_LIMITS = {
|
|
|
2427
3085
|
read: 20,
|
|
2428
3086
|
match: 100
|
|
2429
3087
|
};
|
|
2430
|
-
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
|
|
3088
|
+
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd(), languages = [DEFAULT_LANGUAGE_ID]) {
|
|
2431
3089
|
return {
|
|
2432
3090
|
root: cwd,
|
|
2433
3091
|
cwd,
|
|
2434
3092
|
limits,
|
|
2435
|
-
counts: { list: 0, search: 0, read: 0 }
|
|
3093
|
+
counts: { list: 0, search: 0, read: 0 },
|
|
3094
|
+
languages: languages.length ? languages : [DEFAULT_LANGUAGE_ID]
|
|
2436
3095
|
};
|
|
2437
3096
|
}
|
|
2438
3097
|
|
|
@@ -2469,7 +3128,7 @@ function createTools(ctx, { output, tools }) {
|
|
|
2469
3128
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
2470
3129
|
verifyImplementation: withLogging(
|
|
2471
3130
|
"verifyImplementation",
|
|
2472
|
-
verifyImplementationTool()
|
|
3131
|
+
verifyImplementationTool(ctx)
|
|
2473
3132
|
),
|
|
2474
3133
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
2475
3134
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
@@ -2505,7 +3164,11 @@ async function runAgent(req) {
|
|
|
2505
3164
|
baseURL: PROXY_BASE_URL,
|
|
2506
3165
|
fetch: proxyFetch
|
|
2507
3166
|
});
|
|
2508
|
-
const toolContext = createToolContext(
|
|
3167
|
+
const toolContext = createToolContext(
|
|
3168
|
+
void 0,
|
|
3169
|
+
void 0,
|
|
3170
|
+
req.languages
|
|
3171
|
+
);
|
|
2509
3172
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
2510
3173
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
2511
3174
|
const instructions = [
|
|
@@ -2596,8 +3259,11 @@ var detectLanguageSchema = z16.object({
|
|
|
2596
3259
|
var detectLanguage = () => runAgent({
|
|
2597
3260
|
instructions: [
|
|
2598
3261
|
"Analyze the codebase and determine the programming languages and frameworks used",
|
|
2599
|
-
"
|
|
3262
|
+
"Start from the dependency manifests: package.json, pyproject.toml, requirements.txt, Gemfile, composer.json, go.mod, pom.xml, build.gradle(.kts), *.csproj, build.sbt, Package.swift, pubspec.yaml.",
|
|
3263
|
+
"List the language that owns the backend/data code first \u2014 that is the one an ingestion script will be written in.",
|
|
3264
|
+
"If a superset language is found, exclude the subset language. TS-over-JS. Kotlin-over-Java when Kotlin is primary.",
|
|
2600
3265
|
"If a meta-framework is used, exclude the framework. Next-over-React.",
|
|
3266
|
+
"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).",
|
|
2601
3267
|
"Return the exact version",
|
|
2602
3268
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
2603
3269
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
@@ -2646,6 +3312,7 @@ var MODE_CONFIG = {
|
|
|
2646
3312
|
"Analyze the codebase to find the data entities (models) that should be ingested into Algolia.",
|
|
2647
3313
|
"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).",
|
|
2648
3314
|
"Inspect the source of each entity to extract real field names for attributes \u2014 do not guess or leave attributes empty.",
|
|
3315
|
+
"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.",
|
|
2649
3316
|
"Prefer domain models (e.g. Document, Product, User) over framework or infrastructure types.",
|
|
2650
3317
|
"Use as few tools as possible, but do not guess. If you cannot find any entities, return an empty array.",
|
|
2651
3318
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
@@ -2657,8 +3324,9 @@ var MODE_CONFIG = {
|
|
|
2657
3324
|
instructions: [
|
|
2658
3325
|
"Analyze the codebase to determine the single best location to add search UI functionality.",
|
|
2659
3326
|
"Prefer a shared, always-rendered layout location (e.g. a header or navigation component) so search is reachable across the app.",
|
|
2660
|
-
"
|
|
2661
|
-
|
|
3327
|
+
"It may be a client-side component or a server-rendered template \u2014 return whichever the project actually renders its UI from (e.g. /layouts/header.tsx, app/views/layouts/application.html.erb, templates/base.html, resources/views/layouts/app.blade.php, templates/base.html.twig).",
|
|
3328
|
+
"Return one file path as searchImplementationAnalysis.",
|
|
3329
|
+
'Use as few tools as possible, but do not guess. If the project renders no UI at all (an API-only service), say "unknown".',
|
|
2662
3330
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
2663
3331
|
"When done, call reportStatus"
|
|
2664
3332
|
],
|
|
@@ -2667,8 +3335,8 @@ var MODE_CONFIG = {
|
|
|
2667
3335
|
verification: {
|
|
2668
3336
|
instructions: [
|
|
2669
3337
|
"Analyze the codebase to determine which code-quality tools are available to validate changes.",
|
|
2670
|
-
"Look at package.json scripts,
|
|
2671
|
-
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"].',
|
|
3338
|
+
"Look at the dependency manifest and config files for the project's languages: package.json scripts with tsconfig/eslint/prettier, pyproject.toml or setup.cfg (ruff, mypy, black), Gemfile with .rubocop.yml, composer.json scripts (phpstan, pint), go.mod with a golangci-lint config, Maven/Gradle verification tasks, .NET analyzers, analysis_options.yaml.",
|
|
3339
|
+
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"] or ["ruff", "mypy"].',
|
|
2672
3340
|
"Use as few tools as possible, but do not guess. If you cannot find any, return an empty array.",
|
|
2673
3341
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
2674
3342
|
"When done, call reportStatus"
|
|
@@ -2695,7 +3363,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
2695
3363
|
// package.json
|
|
2696
3364
|
var package_default = {
|
|
2697
3365
|
name: "@algolia/wizard",
|
|
2698
|
-
version: "0.
|
|
3366
|
+
version: "0.6.0-rc.51.23",
|
|
2699
3367
|
description: "Magically implement Algolia functionality in your codebase",
|
|
2700
3368
|
type: "module",
|
|
2701
3369
|
engines: {
|
|
@@ -2798,82 +3466,175 @@ function parseEntries(raw) {
|
|
|
2798
3466
|
return raw.split(",").map(clean).filter(Boolean).slice(0, MAX_ENTRIES).map((name) => ({ name, version: "unknown" }));
|
|
2799
3467
|
}
|
|
2800
3468
|
var summarize = (entries) => entries.length ? entries.map((e) => e.name).join(", ") : "none";
|
|
2801
|
-
|
|
3469
|
+
|
|
3470
|
+
// src/actions/confirmLanguage.ts
|
|
3471
|
+
import z19 from "zod";
|
|
3472
|
+
var confirmLanguageSchema = z19.object({
|
|
3473
|
+
languages: detectLanguageSchema.shape.languages
|
|
3474
|
+
});
|
|
3475
|
+
var OTHER_OPTION = "Other";
|
|
3476
|
+
var CURATED_LANGUAGES = Object.values(LANGUAGE_PROFILES).map(
|
|
3477
|
+
(profile2) => profile2.displayName
|
|
3478
|
+
);
|
|
3479
|
+
function isSameLanguage(a, b) {
|
|
3480
|
+
const x = resolveLanguageProfile(a);
|
|
3481
|
+
const y = resolveLanguageProfile(b);
|
|
3482
|
+
if (x && y) return x.id === y.id;
|
|
3483
|
+
if (x || y) return false;
|
|
3484
|
+
const fold = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3485
|
+
return fold(a) !== "" && fold(a) === fold(b);
|
|
3486
|
+
}
|
|
3487
|
+
function confirmed(languages) {
|
|
3488
|
+
track("AI Wizard Language Confirmed", { languages });
|
|
3489
|
+
return { languages };
|
|
3490
|
+
}
|
|
3491
|
+
async function askOtherLanguage(ctx) {
|
|
3492
|
+
let prompt = "enter the language for your ingestion script";
|
|
2802
3493
|
for (; ; ) {
|
|
2803
3494
|
const answer = await ctx.requestUserInput({
|
|
2804
3495
|
prompt,
|
|
2805
3496
|
promptType: "textInput",
|
|
2806
|
-
options: []
|
|
2807
|
-
helpText: 'Comma-separated, e.g. "TypeScript, Node".'
|
|
3497
|
+
options: []
|
|
2808
3498
|
});
|
|
2809
3499
|
if (typeof answer !== "string") {
|
|
2810
|
-
throw new Error("
|
|
3500
|
+
throw new Error("confirmLanguage received an unexpected non-text result");
|
|
2811
3501
|
}
|
|
2812
|
-
const
|
|
2813
|
-
if (
|
|
2814
|
-
prompt = "
|
|
3502
|
+
const name = parseEntries(answer)[0]?.name;
|
|
3503
|
+
if (name) return name;
|
|
3504
|
+
prompt = "please enter a language name:";
|
|
2815
3505
|
}
|
|
2816
3506
|
}
|
|
2817
|
-
|
|
2818
|
-
// src/actions/confirmLanguage.ts
|
|
2819
|
-
import z19 from "zod";
|
|
2820
|
-
var confirmLanguageSchema = z19.object({
|
|
2821
|
-
languages: detectLanguageSchema.shape.languages
|
|
2822
|
-
});
|
|
2823
3507
|
async function confirmLanguage(ctx) {
|
|
2824
3508
|
const detected = ctx.getStepOutput("project-scan");
|
|
2825
|
-
const
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
3509
|
+
const detectedLanguages = detected.languages ?? [];
|
|
3510
|
+
const others = (chosen) => detectedLanguages.filter((l) => !isSameLanguage(l.name, chosen));
|
|
3511
|
+
const primary = detectedLanguages[0];
|
|
3512
|
+
if (primary) {
|
|
3513
|
+
const accepted = await ctx.requestUserInput({
|
|
3514
|
+
prompt: `Write the ingestion script in ${primary.name}?`,
|
|
3515
|
+
promptType: "acceptReject",
|
|
3516
|
+
options: [`Confirm ${primary.name}`, "Use a different language"],
|
|
3517
|
+
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0],
|
|
3518
|
+
messages: detectedLanguages.length > 1 ? [`Detected: ${summarize(detectedLanguages)}`] : []
|
|
3519
|
+
});
|
|
3520
|
+
if (accepted === true) return confirmed(detectedLanguages);
|
|
3521
|
+
}
|
|
3522
|
+
const options = [...CURATED_LANGUAGES];
|
|
3523
|
+
for (const language of detectedLanguages) {
|
|
3524
|
+
if (!options.some((o) => isSameLanguage(o, language.name))) {
|
|
3525
|
+
options.push(language.name);
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
options.push(OTHER_OPTION);
|
|
3529
|
+
const detectedFor = (option) => detectedLanguages.find((l) => isSameLanguage(option, l.name));
|
|
3530
|
+
const secondary = options.map(
|
|
3531
|
+
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
3532
|
+
);
|
|
3533
|
+
const defaultSelectedIndex = Math.max(
|
|
3534
|
+
options.findIndex((o) => detectedFor(o)),
|
|
3535
|
+
0
|
|
3536
|
+
);
|
|
3537
|
+
const selection = await ctx.requestUserInput({
|
|
3538
|
+
prompt: "select the language for your ingestion script",
|
|
3539
|
+
promptType: "multipleChoice",
|
|
3540
|
+
options,
|
|
3541
|
+
secondary,
|
|
3542
|
+
defaultSelectedIndex
|
|
2836
3543
|
});
|
|
2837
|
-
|
|
3544
|
+
if (typeof selection !== "string") {
|
|
3545
|
+
throw new Error("confirmLanguage received an unexpected non-text result");
|
|
3546
|
+
}
|
|
3547
|
+
const name = selection === OTHER_OPTION ? await askOtherLanguage(ctx) : selection;
|
|
3548
|
+
const version = detectedFor(name)?.version ?? "unknown";
|
|
3549
|
+
return confirmed([{ name, version }, ...others(name)]);
|
|
2838
3550
|
}
|
|
2839
3551
|
|
|
2840
3552
|
// src/actions/confirmFramework.ts
|
|
2841
3553
|
import z20 from "zod";
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
var
|
|
2846
|
-
|
|
2847
|
-
"
|
|
2848
|
-
"
|
|
2849
|
-
"
|
|
2850
|
-
"
|
|
2851
|
-
|
|
3554
|
+
|
|
3555
|
+
// src/lib/frameworks.ts
|
|
3556
|
+
var BACKEND_ONLY_FRAMEWORK = "Backend only / API";
|
|
3557
|
+
var FRAMEWORKS = [
|
|
3558
|
+
// Frontend — InstantSearch component flavors.
|
|
3559
|
+
{ name: "Next.js", strategy: "react", aliases: ["next", "nextjs"] },
|
|
3560
|
+
{ name: "React", strategy: "react", aliases: ["reactjs"] },
|
|
3561
|
+
{ name: "Vue", strategy: "vue", aliases: ["vuejs", "nuxt", "nuxtjs"] },
|
|
3562
|
+
{ name: "Angular", strategy: "angular", aliases: ["angularjs"] },
|
|
3563
|
+
// No Svelte InstantSearch flavor exists, so it uses InstantSearch.js.
|
|
3564
|
+
{ name: "Svelte", strategy: "js", aliases: ["sveltekit"] },
|
|
3565
|
+
{
|
|
3566
|
+
name: "Vanilla JS",
|
|
3567
|
+
strategy: "js",
|
|
3568
|
+
aliases: ["vanilla", "javascript", "js", "astro", "vite"]
|
|
3569
|
+
},
|
|
3570
|
+
// Backend — Algolia's official framework integrations. Server-rendered
|
|
3571
|
+
// templates get InstantSearch.js from a CDN.
|
|
3572
|
+
{
|
|
3573
|
+
name: "Rails",
|
|
3574
|
+
strategy: "cdn-template",
|
|
3575
|
+
aliases: ["rubyonrails", "ruby on rails", "erb"]
|
|
3576
|
+
},
|
|
3577
|
+
{ name: "Django", strategy: "cdn-template", aliases: ["jinja", "jinja2"] },
|
|
3578
|
+
{ name: "Laravel", strategy: "cdn-template", aliases: ["blade"] },
|
|
3579
|
+
{ name: "Symfony", strategy: "cdn-template", aliases: ["twig"] },
|
|
3580
|
+
// Mobile — Algolia ships InstantSearch iOS/Android and Dart clients, but the
|
|
3581
|
+
// wizard can't scaffold a native UI, so it points at the docs instead.
|
|
3582
|
+
{ name: "Flutter", strategy: "none", aliases: [] },
|
|
3583
|
+
{ name: "iOS", strategy: "none", aliases: ["swiftui", "uikit"] },
|
|
3584
|
+
{ name: "Android", strategy: "none", aliases: ["jetpack compose", "compose"] },
|
|
3585
|
+
{ name: BACKEND_ONLY_FRAMEWORK, strategy: "cdn-template", aliases: [] }
|
|
2852
3586
|
];
|
|
2853
|
-
var
|
|
3587
|
+
var CURATED_FRAMEWORKS = FRAMEWORKS.map(
|
|
3588
|
+
(f) => f.name
|
|
3589
|
+
);
|
|
2854
3590
|
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2855
|
-
var
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
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);
|
|
3591
|
+
var ALIAS_TO_NAME = /* @__PURE__ */ new Map();
|
|
3592
|
+
for (const framework of FRAMEWORKS) {
|
|
3593
|
+
for (const alias of [framework.name, ...framework.aliases]) {
|
|
3594
|
+
ALIAS_TO_NAME.set(normalize(alias), framework.name);
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
var STRATEGY_BY_NAME = new Map(FRAMEWORKS.map((f) => [f.name, f.strategy]));
|
|
3598
|
+
function canonicalFrameworkName(name) {
|
|
3599
|
+
return ALIAS_TO_NAME.get(normalize(name));
|
|
3600
|
+
}
|
|
3601
|
+
function isSameFramework(a, b) {
|
|
3602
|
+
const x = canonicalFrameworkName(a) ?? normalize(a);
|
|
3603
|
+
const y = canonicalFrameworkName(b) ?? normalize(b);
|
|
2874
3604
|
return x !== "" && x === y;
|
|
2875
|
-
}
|
|
2876
|
-
function
|
|
3605
|
+
}
|
|
3606
|
+
function resolveSearchStrategy(frameworkName, hasJavaScriptInStack) {
|
|
3607
|
+
const canonical = frameworkName ? canonicalFrameworkName(frameworkName) : void 0;
|
|
3608
|
+
const strategy = canonical ? STRATEGY_BY_NAME.get(canonical) : void 0;
|
|
3609
|
+
if (strategy) return strategy;
|
|
3610
|
+
return hasJavaScriptInStack ? "js" : "cdn-template";
|
|
3611
|
+
}
|
|
3612
|
+
function searchDocKey(strategy) {
|
|
3613
|
+
return strategy === "cdn-template" ? "templates" : strategy;
|
|
3614
|
+
}
|
|
3615
|
+
function describeSearchTarget(strategy, frameworkName) {
|
|
3616
|
+
switch (strategy) {
|
|
3617
|
+
case "react":
|
|
3618
|
+
return "React (react-instantsearch)";
|
|
3619
|
+
case "vue":
|
|
3620
|
+
return "Vue (vue-instantsearch)";
|
|
3621
|
+
case "angular":
|
|
3622
|
+
return "Angular (angular-instantsearch)";
|
|
3623
|
+
case "js":
|
|
3624
|
+
return "plain JavaScript (InstantSearch.js)";
|
|
3625
|
+
case "cdn-template":
|
|
3626
|
+
return `${frameworkName ?? "server-rendered"} templates (InstantSearch.js via CDN)`;
|
|
3627
|
+
case "none":
|
|
3628
|
+
return frameworkName ?? "a native mobile app";
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
|
|
3632
|
+
// src/actions/confirmFramework.ts
|
|
3633
|
+
var confirmFrameworkSchema = z20.object({
|
|
3634
|
+
frameworks: detectLanguageSchema.shape.frameworks
|
|
3635
|
+
});
|
|
3636
|
+
var OTHER_OPTION2 = "Other";
|
|
3637
|
+
function confirmed2(name, version) {
|
|
2877
3638
|
const frameworks = [{ name, version: version ?? "unknown" }];
|
|
2878
3639
|
track("AI Wizard Frontend Framework Confirmed", { frameworks });
|
|
2879
3640
|
return { frameworks };
|
|
@@ -2901,7 +3662,7 @@ async function confirmFramework(ctx) {
|
|
|
2901
3662
|
for (const fw of detectedFrameworks) {
|
|
2902
3663
|
if (!options.some((o) => isSameFramework(o, fw.name))) options.push(fw.name);
|
|
2903
3664
|
}
|
|
2904
|
-
options.push(
|
|
3665
|
+
options.push(OTHER_OPTION2);
|
|
2905
3666
|
const detectedFor = (option) => detectedFrameworks.find((fw) => isSameFramework(option, fw.name));
|
|
2906
3667
|
const primary = detectedFrameworks[0];
|
|
2907
3668
|
if (primary) {
|
|
@@ -2911,7 +3672,7 @@ async function confirmFramework(ctx) {
|
|
|
2911
3672
|
options: [`Confirm ${primary.name}`, "Use a different framework"],
|
|
2912
3673
|
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0]
|
|
2913
3674
|
});
|
|
2914
|
-
if (accepted === true) return
|
|
3675
|
+
if (accepted === true) return confirmed2(primary.name, primary.version);
|
|
2915
3676
|
}
|
|
2916
3677
|
const secondary = options.map(
|
|
2917
3678
|
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
@@ -2921,7 +3682,7 @@ async function confirmFramework(ctx) {
|
|
|
2921
3682
|
0
|
|
2922
3683
|
);
|
|
2923
3684
|
const selection = await ctx.requestUserInput({
|
|
2924
|
-
prompt: "select
|
|
3685
|
+
prompt: "select the framework that renders your UI",
|
|
2925
3686
|
promptType: "multipleChoice",
|
|
2926
3687
|
options,
|
|
2927
3688
|
secondary,
|
|
@@ -2930,10 +3691,10 @@ async function confirmFramework(ctx) {
|
|
|
2930
3691
|
if (typeof selection !== "string") {
|
|
2931
3692
|
throw new Error("confirmFramework received an unexpected non-text result");
|
|
2932
3693
|
}
|
|
2933
|
-
if (selection ===
|
|
2934
|
-
return
|
|
3694
|
+
if (selection === OTHER_OPTION2) {
|
|
3695
|
+
return confirmed2(await askOtherFramework(ctx));
|
|
2935
3696
|
}
|
|
2936
|
-
return
|
|
3697
|
+
return confirmed2(selection, detectedFor(selection)?.version);
|
|
2937
3698
|
}
|
|
2938
3699
|
|
|
2939
3700
|
// src/actions/promptUser.ts
|
|
@@ -3026,15 +3787,15 @@ async function confirmEntities(ctx) {
|
|
|
3026
3787
|
onSubmit: () => {
|
|
3027
3788
|
}
|
|
3028
3789
|
});
|
|
3029
|
-
const
|
|
3030
|
-
if (
|
|
3790
|
+
const confirmed3 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
|
|
3791
|
+
if (confirmed3.length === 0) {
|
|
3031
3792
|
throw new Error("User cancelled entity selection \u2014 analysis halted.");
|
|
3032
3793
|
}
|
|
3033
|
-
ctx.setUserInput("confirmedEntities",
|
|
3794
|
+
ctx.setUserInput("confirmedEntities", confirmed3);
|
|
3034
3795
|
track("AI Wizard Entities Confirmed", {
|
|
3035
|
-
entities: toEntitySummary(
|
|
3796
|
+
entities: toEntitySummary(confirmed3)
|
|
3036
3797
|
});
|
|
3037
|
-
return { ingestionAnalysis: entities, confirmedEntities:
|
|
3798
|
+
return { ingestionAnalysis: entities, confirmedEntities: confirmed3 };
|
|
3038
3799
|
}
|
|
3039
3800
|
|
|
3040
3801
|
// src/actions/review.ts
|
|
@@ -3058,7 +3819,7 @@ ${JSON.stringify(s.output, null, 2)}`
|
|
|
3058
3819
|
}
|
|
3059
3820
|
function formatReviewSummary(result) {
|
|
3060
3821
|
const nextStepLines = result.nextSteps.map((step) => {
|
|
3061
|
-
const isIngestCommand = step.includes(".algolia-wizard/
|
|
3822
|
+
const isIngestCommand = step.includes(".algolia-wizard/") || step.includes("AlgoliaWizardIngest");
|
|
3062
3823
|
const isWorktreeCommand = step.includes("/worktrees/");
|
|
3063
3824
|
return {
|
|
3064
3825
|
text: `\u2192 ${step}`,
|
|
@@ -3101,12 +3862,13 @@ import z24 from "zod";
|
|
|
3101
3862
|
|
|
3102
3863
|
// src/lib/worktree.ts
|
|
3103
3864
|
import { execFile, spawn as spawn3 } from "node:child_process";
|
|
3104
|
-
import {
|
|
3865
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
3866
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir4, readFile as readFile8, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3105
3867
|
import {
|
|
3106
3868
|
basename as basename2,
|
|
3107
3869
|
dirname as dirname7,
|
|
3108
3870
|
isAbsolute as isAbsolute2,
|
|
3109
|
-
join as
|
|
3871
|
+
join as join12,
|
|
3110
3872
|
relative as relative2,
|
|
3111
3873
|
resolve as resolve3
|
|
3112
3874
|
} from "node:path";
|
|
@@ -3140,8 +3902,8 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
3140
3902
|
return out.trim().length > 0;
|
|
3141
3903
|
}
|
|
3142
3904
|
async function pruneOldWorktrees(repoRoot) {
|
|
3143
|
-
const dir =
|
|
3144
|
-
const stale = (await
|
|
3905
|
+
const dir = join12(stateDir(repoRoot), "worktrees");
|
|
3906
|
+
const stale = (await readdir4(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
3145
3907
|
for (const slug of stale) {
|
|
3146
3908
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
3147
3909
|
try {
|
|
@@ -3151,7 +3913,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3151
3913
|
"worktree",
|
|
3152
3914
|
"remove",
|
|
3153
3915
|
"--force",
|
|
3154
|
-
|
|
3916
|
+
join12(dir, slug)
|
|
3155
3917
|
]);
|
|
3156
3918
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
3157
3919
|
} catch (err) {
|
|
@@ -3165,24 +3927,19 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3165
3927
|
async function createWorktree(repoRoot) {
|
|
3166
3928
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
3167
3929
|
const dirSlug = branch.replace(/\//g, "-");
|
|
3168
|
-
const path =
|
|
3930
|
+
const path = join12(stateDir(repoRoot), "worktrees", dirSlug);
|
|
3169
3931
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
3170
3932
|
await pruneOldWorktrees(repoRoot);
|
|
3171
3933
|
await mkdir6(dirname7(path), { recursive: true });
|
|
3172
3934
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
3173
3935
|
return { path, branch };
|
|
3174
3936
|
}
|
|
3175
|
-
|
|
3176
|
-
try {
|
|
3177
|
-
await readPackageJson(worktreePath);
|
|
3178
|
-
} catch {
|
|
3179
|
-
return { ok: true, output: "no package.json; skipped install" };
|
|
3180
|
-
}
|
|
3181
|
-
const pm = await detectPackageManager(worktreePath);
|
|
3937
|
+
function spawnStep(worktreePath, argv) {
|
|
3182
3938
|
return new Promise((resolve4) => {
|
|
3183
3939
|
let output = "";
|
|
3184
|
-
const child = spawn3(
|
|
3940
|
+
const child = spawn3(argv[0], [...argv.slice(1)], {
|
|
3185
3941
|
cwd: worktreePath,
|
|
3942
|
+
shell: false,
|
|
3186
3943
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3187
3944
|
});
|
|
3188
3945
|
child.stdout?.on("data", (d) => output += d);
|
|
@@ -3191,7 +3948,7 @@ async function installWorktreeDeps(worktreePath) {
|
|
|
3191
3948
|
"error",
|
|
3192
3949
|
(err) => resolve4({
|
|
3193
3950
|
ok: false,
|
|
3194
|
-
output: `Failed to run ${
|
|
3951
|
+
output: `Failed to run ${argv.join(" ")}: ${err.message}`
|
|
3195
3952
|
})
|
|
3196
3953
|
);
|
|
3197
3954
|
child.on(
|
|
@@ -3200,8 +3957,41 @@ async function installWorktreeDeps(worktreePath) {
|
|
|
3200
3957
|
);
|
|
3201
3958
|
});
|
|
3202
3959
|
}
|
|
3203
|
-
|
|
3204
|
-
|
|
3960
|
+
async function installWorktreeDeps(worktreePath, toolchain) {
|
|
3961
|
+
const { profile: profile2, installSteps, packageManager } = toolchain;
|
|
3962
|
+
const declared = packageManager.dependency.mode === "agent-declares" ? packageManager.dependency.file : void 0;
|
|
3963
|
+
const haveSomethingToInstall = await hasProfileManifest(worktreePath, profile2) || declared !== void 0 && existsSync4(join12(worktreePath, declared));
|
|
3964
|
+
if (!haveSomethingToInstall) {
|
|
3965
|
+
return {
|
|
3966
|
+
ok: true,
|
|
3967
|
+
output: `no ${profile2.displayName} manifest; skipped install`
|
|
3968
|
+
};
|
|
3969
|
+
}
|
|
3970
|
+
if (installSteps.length === 0) {
|
|
3971
|
+
return {
|
|
3972
|
+
ok: true,
|
|
3973
|
+
output: `${profile2.displayName} (${toolchain.packageManager.id}) has no wizard-run install step`
|
|
3974
|
+
};
|
|
3975
|
+
}
|
|
3976
|
+
const outputs = [];
|
|
3977
|
+
for (const step of installSteps) {
|
|
3978
|
+
if (step.requiresFile && !existsSync4(join12(worktreePath, step.requiresFile)))
|
|
3979
|
+
continue;
|
|
3980
|
+
const result = await spawnStep(worktreePath, step.argv);
|
|
3981
|
+
if (result.output) outputs.push(result.output);
|
|
3982
|
+
if (result.ok) continue;
|
|
3983
|
+
if (step.optional) {
|
|
3984
|
+
logger.warn(
|
|
3985
|
+
{ step: step.argv.join(" "), output: result.output },
|
|
3986
|
+
"installWorktreeDeps: optional install step failed; continuing"
|
|
3987
|
+
);
|
|
3988
|
+
continue;
|
|
3989
|
+
}
|
|
3990
|
+
return { ok: false, output: outputs.join("\n").trim() };
|
|
3991
|
+
}
|
|
3992
|
+
return { ok: true, output: outputs.join("\n").trim() };
|
|
3993
|
+
}
|
|
3994
|
+
function validateIngestEntrypoint(worktreePath, entrypoint, allowedExtensions) {
|
|
3205
3995
|
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
3206
3996
|
return {
|
|
3207
3997
|
ok: false,
|
|
@@ -3216,18 +4006,29 @@ function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
|
3216
4006
|
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
3217
4007
|
};
|
|
3218
4008
|
}
|
|
4009
|
+
if (allowedExtensions?.length && !allowedExtensions.some((ext) => entrypoint.endsWith(ext))) {
|
|
4010
|
+
return {
|
|
4011
|
+
ok: false,
|
|
4012
|
+
reason: `entrypoint "${entrypoint}" is not one of ${allowedExtensions.join(", ")}`
|
|
4013
|
+
};
|
|
4014
|
+
}
|
|
3219
4015
|
return { ok: true, target };
|
|
3220
4016
|
}
|
|
3221
|
-
async function runIngestScript(worktreePath,
|
|
3222
|
-
|
|
4017
|
+
async function runIngestScript(worktreePath, toolchain, entrypoint, env = {}) {
|
|
4018
|
+
const { ingest, profile: profile2, packageManager } = toolchain;
|
|
4019
|
+
if (ingest.kind !== "auto") {
|
|
3223
4020
|
return {
|
|
3224
4021
|
ran: false,
|
|
3225
4022
|
ok: false,
|
|
3226
4023
|
output: "",
|
|
3227
|
-
reason:
|
|
4024
|
+
reason: `${profile2.displayName} (${packageManager.id}) projects must be run manually: ${ingest.runCommand}`
|
|
3228
4025
|
};
|
|
3229
4026
|
}
|
|
3230
|
-
const validated = validateIngestEntrypoint(
|
|
4027
|
+
const validated = validateIngestEntrypoint(
|
|
4028
|
+
worktreePath,
|
|
4029
|
+
entrypoint,
|
|
4030
|
+
ingest.entrypointExtensions
|
|
4031
|
+
);
|
|
3231
4032
|
if (!validated.ok) {
|
|
3232
4033
|
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
3233
4034
|
}
|
|
@@ -3248,9 +4049,10 @@ async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
|
3248
4049
|
reason: `entrypoint "${entrypoint}" does not exist`
|
|
3249
4050
|
};
|
|
3250
4051
|
}
|
|
4052
|
+
const argv = resolveIngestArgv(ingest, entrypoint);
|
|
3251
4053
|
return new Promise((resolveRun) => {
|
|
3252
4054
|
let output = "";
|
|
3253
|
-
const child = spawn3(
|
|
4055
|
+
const child = spawn3(argv[0], argv.slice(1), {
|
|
3254
4056
|
cwd: worktreePath,
|
|
3255
4057
|
shell: false,
|
|
3256
4058
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -3263,7 +4065,7 @@ async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
|
3263
4065
|
(err) => resolveRun({
|
|
3264
4066
|
ran: true,
|
|
3265
4067
|
ok: false,
|
|
3266
|
-
output: `Failed to run ${
|
|
4068
|
+
output: `Failed to run ${argv.join(" ")}: ${err.message}`
|
|
3267
4069
|
})
|
|
3268
4070
|
);
|
|
3269
4071
|
child.on(
|
|
@@ -3285,8 +4087,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3285
4087
|
} catch {
|
|
3286
4088
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
3287
4089
|
}
|
|
3288
|
-
const relPath =
|
|
3289
|
-
const dest =
|
|
4090
|
+
const relPath = join12(ingestDir, basename2(source));
|
|
4091
|
+
const dest = join12(worktreePath, relPath);
|
|
3290
4092
|
try {
|
|
3291
4093
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
3292
4094
|
await copyFile(source, dest);
|
|
@@ -3302,7 +4104,7 @@ function hasEnvVar(content, name) {
|
|
|
3302
4104
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
3303
4105
|
}
|
|
3304
4106
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
3305
|
-
const target =
|
|
4107
|
+
const target = join12(worktreePath, ".env");
|
|
3306
4108
|
let existing = "";
|
|
3307
4109
|
try {
|
|
3308
4110
|
existing = await readFile8(target, "utf8");
|
|
@@ -3422,69 +4224,33 @@ async function resolveSearchOnlyKey(index) {
|
|
|
3422
4224
|
}
|
|
3423
4225
|
|
|
3424
4226
|
// src/lib/algoliaDocs.ts
|
|
3425
|
-
import { readFileSync,
|
|
3426
|
-
import { dirname as dirname8, join as
|
|
4227
|
+
import { readFileSync, existsSync as existsSync5 } from "node:fs";
|
|
4228
|
+
import { dirname as dirname8, join as join13 } from "node:path";
|
|
3427
4229
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3428
|
-
var DOCS_SUBPATH =
|
|
4230
|
+
var DOCS_SUBPATH = join13("docs", "algolia-sdk");
|
|
3429
4231
|
function findDocsDir() {
|
|
3430
4232
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
3431
4233
|
for (; ; ) {
|
|
3432
|
-
const candidate =
|
|
3433
|
-
if (
|
|
4234
|
+
const candidate = join13(dir, DOCS_SUBPATH);
|
|
4235
|
+
if (existsSync5(candidate)) return candidate;
|
|
3434
4236
|
const parent = dirname8(dir);
|
|
3435
4237
|
if (parent === dir) return void 0;
|
|
3436
4238
|
dir = parent;
|
|
3437
4239
|
}
|
|
3438
4240
|
}
|
|
3439
|
-
function
|
|
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) {
|
|
4241
|
+
function getNamedDoc(name, key) {
|
|
3458
4242
|
const docsDir = findDocsDir();
|
|
3459
4243
|
if (!docsDir) {
|
|
3460
4244
|
logger.warn("docs/algolia-sdk not found");
|
|
3461
4245
|
return "";
|
|
3462
4246
|
}
|
|
3463
|
-
const file =
|
|
3464
|
-
if (!
|
|
3465
|
-
logger.warn({ name,
|
|
4247
|
+
const file = join13(docsDir, `${name}-${key}.md`);
|
|
4248
|
+
if (!existsSync5(file)) {
|
|
4249
|
+
logger.warn({ name, key }, "named SDK reference not found");
|
|
3466
4250
|
return "";
|
|
3467
4251
|
}
|
|
3468
4252
|
return readFileSync(file, "utf8").trim();
|
|
3469
4253
|
}
|
|
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
|
-
}
|
|
3488
4254
|
|
|
3489
4255
|
// src/actions/implement.ts
|
|
3490
4256
|
var implementSchema = z24.object({
|
|
@@ -3519,12 +4285,11 @@ var implementSchema = z24.object({
|
|
|
3519
4285
|
});
|
|
3520
4286
|
var implementationOutputSchema = z24.object({
|
|
3521
4287
|
summary: z24.string(),
|
|
3522
|
-
// Ingestion only:
|
|
3523
|
-
//
|
|
3524
|
-
//
|
|
3525
|
-
//
|
|
3526
|
-
// the agent
|
|
3527
|
-
runtime: z24.enum(INGEST_RUNTIMES).optional(),
|
|
4288
|
+
// Ingestion only: the script the wizard should run, as a bare path — never a
|
|
4289
|
+
// command string, and never the interpreter. The command comes from the
|
|
4290
|
+
// resolved language toolchain (a registry constant); this path is validated to
|
|
4291
|
+
// a worktree-relative file with a runnable extension and substituted into it.
|
|
4292
|
+
// So the agent contributes no part of the command that gets executed.
|
|
3528
4293
|
entrypoint: z24.string().optional()
|
|
3529
4294
|
});
|
|
3530
4295
|
var verificationOutputSchema = z24.object({
|
|
@@ -3534,28 +4299,9 @@ var verificationOutputSchema = z24.object({
|
|
|
3534
4299
|
});
|
|
3535
4300
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
3536
4301
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
3537
|
-
var
|
|
3538
|
-
function
|
|
3539
|
-
|
|
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) {
|
|
4302
|
+
var INGEST_DIR2 = ".algolia-wizard";
|
|
4303
|
+
function publicEnvPrefix(language, strategy) {
|
|
4304
|
+
if (strategy === "cdn-template" || strategy === "none") return "";
|
|
3559
4305
|
const frameworkNames = language.frameworks.map(
|
|
3560
4306
|
(framework) => framework.name.toLowerCase()
|
|
3561
4307
|
);
|
|
@@ -3573,8 +4319,8 @@ function publicEnvPrefix(language) {
|
|
|
3573
4319
|
}
|
|
3574
4320
|
return "PUBLIC_";
|
|
3575
4321
|
}
|
|
3576
|
-
function searchEnvVars(language, appId, searchKey) {
|
|
3577
|
-
const prefix = publicEnvPrefix(language);
|
|
4322
|
+
function searchEnvVars(language, strategy, appId, searchKey) {
|
|
4323
|
+
const prefix = publicEnvPrefix(language, strategy);
|
|
3578
4324
|
return [
|
|
3579
4325
|
{
|
|
3580
4326
|
name: `${prefix}ALGOLIA_APP_ID`,
|
|
@@ -3586,6 +4332,42 @@ function searchEnvVars(language, appId, searchKey) {
|
|
|
3586
4332
|
}
|
|
3587
4333
|
];
|
|
3588
4334
|
}
|
|
4335
|
+
async function resolveIngestionProfile(ctx, language, repoRoot) {
|
|
4336
|
+
const fallback = LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID];
|
|
4337
|
+
const confirmed3 = language.languages.map((l) => resolveLanguageProfile(l.name)).filter((p) => p !== void 0);
|
|
4338
|
+
const onDisk = await detectProfilesFromManifests(repoRoot);
|
|
4339
|
+
const onDiskIds = new Set(onDisk.map((p) => p.id));
|
|
4340
|
+
const candidates = [
|
|
4341
|
+
...new Map(
|
|
4342
|
+
confirmed3.filter((p) => onDiskIds.has(p.id)).map((p) => [p.id, p])
|
|
4343
|
+
).values()
|
|
4344
|
+
];
|
|
4345
|
+
if (candidates.length === 0) {
|
|
4346
|
+
const chosen = confirmed3[0] ?? onDisk[0] ?? fallback;
|
|
4347
|
+
logger.warn(
|
|
4348
|
+
{
|
|
4349
|
+
confirmed: language.languages.map((l) => l.name),
|
|
4350
|
+
onDisk: onDisk.map((p) => p.id),
|
|
4351
|
+
chosen: chosen.id
|
|
4352
|
+
},
|
|
4353
|
+
"implement: no confirmed language matched a manifest on disk; falling back"
|
|
4354
|
+
);
|
|
4355
|
+
return chosen;
|
|
4356
|
+
}
|
|
4357
|
+
if (candidates.length === 1) return candidates[0];
|
|
4358
|
+
const backends = candidates.filter((p) => p.id !== DEFAULT_LANGUAGE_ID);
|
|
4359
|
+
if (backends.length === 1) return backends[0];
|
|
4360
|
+
if (backends.length === 0) return candidates[0];
|
|
4361
|
+
const options = backends.map((p) => p.displayName);
|
|
4362
|
+
const selection = await ctx.requestUserInput({
|
|
4363
|
+
prompt: "Which language should the ingestion script use?",
|
|
4364
|
+
promptType: "multipleChoice",
|
|
4365
|
+
options,
|
|
4366
|
+
defaultSelectedIndex: 0
|
|
4367
|
+
});
|
|
4368
|
+
const picked = typeof selection === "string" ? backends.find((p) => p.displayName === selection) : void 0;
|
|
4369
|
+
return picked ?? backends[0];
|
|
4370
|
+
}
|
|
3589
4371
|
function baseInstructions(input) {
|
|
3590
4372
|
return [
|
|
3591
4373
|
`Target Algolia index: ${input.targetIndex}`,
|
|
@@ -3613,37 +4395,48 @@ function sourceSpecificInstructions(input) {
|
|
|
3613
4395
|
generated: [
|
|
3614
4396
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
3615
4397
|
"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.",
|
|
3616
|
-
"In the script, read and parse each returned file path at runtime
|
|
4398
|
+
"In the script, read and parse each returned JSON file path at runtime using the idiomatic file read for the language you are writing in (the SDK reference above shows one) instead of inlining the records as literals.",
|
|
3617
4399
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
3618
4400
|
]
|
|
3619
4401
|
};
|
|
3620
4402
|
return byLine[input.ingestionSource];
|
|
3621
4403
|
}
|
|
3622
4404
|
function ingestionInstructions(input) {
|
|
4405
|
+
const { ingestionProfile: profile2, toolchain } = input;
|
|
4406
|
+
const { ingest } = toolchain;
|
|
4407
|
+
const extensions = ingest.entrypointExtensions.join(", ");
|
|
4408
|
+
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.`;
|
|
3623
4409
|
return [
|
|
3624
4410
|
...input.confirmed && input.confirmed.length ? [
|
|
3625
|
-
`
|
|
4411
|
+
`Write the ingestion script in ${profile2.displayName}, at "${ingestScriptDir(profile2)}/" in the repo.`,
|
|
3626
4412
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
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
|
-
|
|
4413
|
+
`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. ${profile2.envReadInstruction} The wizard sets these when it runs the script.`,
|
|
4414
|
+
`Use the official Algolia ${profile2.displayName} client (${profile2.sdk.packageName}). Do not use the raw HTTP API, and do not use a client for another language.`,
|
|
3629
4415
|
"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.",
|
|
3630
|
-
getNamedDoc("save-records",
|
|
3631
|
-
|
|
4416
|
+
getNamedDoc("save-records", profile2.sdk.docKey),
|
|
4417
|
+
dependencyInstruction(toolchain),
|
|
3632
4418
|
"The summary should be extremely concise.",
|
|
3633
|
-
|
|
4419
|
+
runInstruction,
|
|
3634
4420
|
...sourceSpecificInstructions(input)
|
|
3635
4421
|
] : []
|
|
3636
4422
|
];
|
|
3637
4423
|
}
|
|
3638
4424
|
function searchInstructions(input) {
|
|
3639
|
-
const doc =
|
|
4425
|
+
const doc = getNamedDoc(
|
|
4426
|
+
"instantsearch-setup",
|
|
4427
|
+
searchDocKey(input.searchStrategy)
|
|
4428
|
+
);
|
|
4429
|
+
const isTemplate = input.searchStrategy === "cdn-template";
|
|
4430
|
+
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.`;
|
|
3640
4431
|
return [
|
|
3641
4432
|
"Implement an in-app Algolia search experience.",
|
|
3642
|
-
`Build the search UI for ${input.
|
|
3643
|
-
"Follow the Algolia
|
|
4433
|
+
`Build the search UI for ${describeSearchTarget(input.searchStrategy, input.frameworkName)}.`,
|
|
4434
|
+
"Follow the Algolia reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3644
4435
|
doc,
|
|
3645
|
-
|
|
3646
|
-
|
|
4436
|
+
placement,
|
|
4437
|
+
`It needs at least a working SearchBox and Hits against the "${input.targetIndex}" index.`,
|
|
4438
|
+
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.',
|
|
4439
|
+
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.",
|
|
3647
4440
|
// appId always resolves (loadActiveProfile throws otherwise); only the
|
|
3648
4441
|
// search-only key is best-effort and can fall back to a placeholder.
|
|
3649
4442
|
`Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
@@ -3651,8 +4444,7 @@ function searchInstructions(input) {
|
|
|
3651
4444
|
// resolved app id / search-only key into ".env" under these exact names
|
|
3652
4445
|
// right after this step, so a renamed prefix here would leave the code
|
|
3653
4446
|
// reading a var the wizard never wrote.
|
|
3654
|
-
`Use exactly these
|
|
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.',
|
|
4447
|
+
`Use exactly these env var names: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3656
4448
|
"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."
|
|
3657
4449
|
];
|
|
3658
4450
|
}
|
|
@@ -3660,7 +4452,7 @@ function verificationInstructions(input) {
|
|
|
3660
4452
|
return [
|
|
3661
4453
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
3662
4454
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
3663
|
-
|
|
4455
|
+
`Call verifyImplementation at least once; it runs the mechanical checks available for this repo's languages (${input.verificationLanguages.join(", ")}) and returns per-check results plus an aggregate ok.`,
|
|
3664
4456
|
"For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
|
|
3665
4457
|
"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.",
|
|
3666
4458
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
@@ -3715,8 +4507,8 @@ function formatSummary(useCase, summary) {
|
|
|
3715
4507
|
const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
|
|
3716
4508
|
return `${label}: ${summary}`;
|
|
3717
4509
|
}
|
|
3718
|
-
function buildIngestCommand(worktree,
|
|
3719
|
-
return `cd ${shellQuote(worktree)} && ${
|
|
4510
|
+
function buildIngestCommand(worktree, toolchain, entrypoint) {
|
|
4511
|
+
return `cd ${shellQuote(worktree)} && ${describeIngestCommand(toolchain.ingest, entrypoint)}`;
|
|
3720
4512
|
}
|
|
3721
4513
|
function parseIngestRecordCount(output) {
|
|
3722
4514
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
@@ -3798,7 +4590,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3798
4590
|
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
3799
4591
|
}
|
|
3800
4592
|
const normalized = normalizeFindingPaths(findings);
|
|
3801
|
-
const
|
|
4593
|
+
const confirmed3 = normalized.confirmedEntities;
|
|
3802
4594
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
3803
4595
|
let appId;
|
|
3804
4596
|
let searchKey;
|
|
@@ -3822,7 +4614,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3822
4614
|
const copied = await copyUploadIntoWorktree(
|
|
3823
4615
|
repoRoot,
|
|
3824
4616
|
worktree,
|
|
3825
|
-
|
|
4617
|
+
INGEST_DIR2,
|
|
3826
4618
|
uploadSourcePath ?? ""
|
|
3827
4619
|
);
|
|
3828
4620
|
if (copied.ok) {
|
|
@@ -3836,26 +4628,59 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3836
4628
|
);
|
|
3837
4629
|
}
|
|
3838
4630
|
}
|
|
4631
|
+
const ingestionProfile = await resolveIngestionProfile(
|
|
4632
|
+
ctx,
|
|
4633
|
+
language,
|
|
4634
|
+
worktree
|
|
4635
|
+
);
|
|
4636
|
+
const toolchain = await resolveToolchain(worktree, ingestionProfile);
|
|
4637
|
+
const verificationLanguages = [
|
|
4638
|
+
.../* @__PURE__ */ new Set([
|
|
4639
|
+
ingestionProfile.id,
|
|
4640
|
+
...(await detectProfilesFromManifests(worktree)).map((p) => p.id)
|
|
4641
|
+
])
|
|
4642
|
+
];
|
|
4643
|
+
const frameworkName = language.frameworks[0]?.name;
|
|
4644
|
+
const searchStrategy = resolveSearchStrategy(
|
|
4645
|
+
frameworkName,
|
|
4646
|
+
verificationLanguages.includes(DEFAULT_LANGUAGE_ID)
|
|
4647
|
+
);
|
|
4648
|
+
logger.info(
|
|
4649
|
+
{
|
|
4650
|
+
language: ingestionProfile.id,
|
|
4651
|
+
packageManager: toolchain.packageManager.id,
|
|
4652
|
+
ingest: toolchain.ingest.kind,
|
|
4653
|
+
framework: frameworkName,
|
|
4654
|
+
searchStrategy
|
|
4655
|
+
},
|
|
4656
|
+
"implement: resolved ingestion toolchain and search strategy"
|
|
4657
|
+
);
|
|
3839
4658
|
const input = {
|
|
3840
4659
|
findings: normalized,
|
|
3841
|
-
confirmed:
|
|
4660
|
+
confirmed: confirmed3,
|
|
3842
4661
|
searchLocation,
|
|
3843
4662
|
targetIndex,
|
|
3844
4663
|
language,
|
|
3845
4664
|
appId,
|
|
3846
4665
|
searchKey,
|
|
3847
|
-
searchEnvVars: searchEnvVars(language, appId, searchKey),
|
|
3848
|
-
ingestDir:
|
|
4666
|
+
searchEnvVars: searchEnvVars(language, searchStrategy, appId, searchKey),
|
|
4667
|
+
ingestDir: INGEST_DIR2,
|
|
3849
4668
|
ingestionSource,
|
|
3850
4669
|
uploadFilePath,
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
4670
|
+
searchStrategy,
|
|
4671
|
+
frameworkName,
|
|
4672
|
+
ingestionProfile,
|
|
4673
|
+
toolchain,
|
|
4674
|
+
verificationLanguages
|
|
3854
4675
|
};
|
|
4676
|
+
const searchToolchain = searchStrategy === "cdn-template" || searchStrategy === "none" ? void 0 : ingestionProfile.id === DEFAULT_LANGUAGE_ID ? toolchain : await resolveToolchain(
|
|
4677
|
+
worktree,
|
|
4678
|
+
LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID]
|
|
4679
|
+
);
|
|
4680
|
+
const toolchainForUseCase = (useCase) => useCase === "search" ? searchToolchain : toolchain;
|
|
3855
4681
|
const summaries = [];
|
|
3856
4682
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
3857
4683
|
let agentRuns = 0;
|
|
3858
|
-
let ingestRuntime;
|
|
3859
4684
|
let ingestEntrypoint;
|
|
3860
4685
|
let ingestScriptRan = false;
|
|
3861
4686
|
let ingestRecordCount;
|
|
@@ -3874,13 +4699,16 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3874
4699
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
3875
4700
|
outputSchema: implementationOutputSchema
|
|
3876
4701
|
});
|
|
4702
|
+
const useCaseToolchain = toolchainForUseCase(currentUseCase);
|
|
4703
|
+
if (!useCaseToolchain) return result;
|
|
3877
4704
|
ctx.notify({
|
|
3878
4705
|
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
3879
4706
|
});
|
|
3880
4707
|
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
3881
|
-
useCase: currentUseCase
|
|
4708
|
+
useCase: currentUseCase,
|
|
4709
|
+
language: useCaseToolchain.profile.id
|
|
3882
4710
|
});
|
|
3883
|
-
const install = await installWorktreeDeps(worktree);
|
|
4711
|
+
const install = await installWorktreeDeps(worktree, useCaseToolchain);
|
|
3884
4712
|
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
3885
4713
|
if (!install.ok) {
|
|
3886
4714
|
installFailed = true;
|
|
@@ -3897,15 +4725,16 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3897
4725
|
return runAgent({
|
|
3898
4726
|
instructions: buildAgentInstructions("verification", input),
|
|
3899
4727
|
tools: toolsForUseCase("verification"),
|
|
3900
|
-
outputSchema: verificationOutputSchema
|
|
4728
|
+
outputSchema: verificationOutputSchema,
|
|
4729
|
+
// So verifyImplementation runs this repo's checks, not just npm scripts.
|
|
4730
|
+
languages: input.verificationLanguages
|
|
3901
4731
|
});
|
|
3902
4732
|
}
|
|
3903
4733
|
if (useCases.includes("ingestion")) {
|
|
3904
|
-
const { summary,
|
|
4734
|
+
const { summary, entrypoint } = await runImplementationUseCase("ingestion");
|
|
3905
4735
|
summaries.push(formatSummary("ingestion", summary));
|
|
3906
|
-
ingestRuntime = runtime;
|
|
3907
4736
|
ingestEntrypoint = entrypoint;
|
|
3908
|
-
if (
|
|
4737
|
+
if (ingestEntrypoint && toolchain.ingest.kind === "auto" && !installFailed) {
|
|
3909
4738
|
ctx.clearNotices();
|
|
3910
4739
|
const runNow = await ctx.requestUserInput({
|
|
3911
4740
|
prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
|
|
@@ -3917,13 +4746,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3917
4746
|
const profile2 = await loadActiveProfile();
|
|
3918
4747
|
ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
|
|
3919
4748
|
const scriptLogId = ctx.logStart("runIngestScript", {
|
|
3920
|
-
|
|
4749
|
+
language: ingestionProfile.id,
|
|
3921
4750
|
entrypoint: ingestEntrypoint
|
|
3922
4751
|
});
|
|
3923
4752
|
const startedAt = Date.now();
|
|
3924
4753
|
const run = await runIngestScript(
|
|
3925
4754
|
worktree,
|
|
3926
|
-
|
|
4755
|
+
toolchain,
|
|
3927
4756
|
ingestEntrypoint,
|
|
3928
4757
|
{
|
|
3929
4758
|
[APP_ID_VAR]: profile2.appId,
|
|
@@ -3937,7 +4766,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3937
4766
|
ingestRecordCount = parseIngestRecordCount(run.output);
|
|
3938
4767
|
if (ingestRecordCount != null) {
|
|
3939
4768
|
track("AI Wizard Ingest Successful", {
|
|
3940
|
-
entity_name:
|
|
4769
|
+
entity_name: confirmed3?.map((e) => e.name).join(", ") || "unknown",
|
|
3941
4770
|
record_count: ingestRecordCount,
|
|
3942
4771
|
duration_ms: ingestDurationMs
|
|
3943
4772
|
});
|
|
@@ -3950,7 +4779,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3950
4779
|
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run.reason}`;
|
|
3951
4780
|
logger.warn(
|
|
3952
4781
|
{
|
|
3953
|
-
|
|
4782
|
+
language: ingestionProfile.id,
|
|
3954
4783
|
entrypoint: ingestEntrypoint,
|
|
3955
4784
|
reason: run.reason
|
|
3956
4785
|
},
|
|
@@ -3973,7 +4802,7 @@ ${run.output}` : status;
|
|
|
3973
4802
|
outcomeMessage = `\u274C Ingestion failed.${run.output ? ` ${run.output}` : ""}`;
|
|
3974
4803
|
logger.warn(
|
|
3975
4804
|
{
|
|
3976
|
-
|
|
4805
|
+
language: ingestionProfile.id,
|
|
3977
4806
|
entrypoint: ingestEntrypoint,
|
|
3978
4807
|
output: run.output
|
|
3979
4808
|
},
|
|
@@ -3990,10 +4819,15 @@ ${run.output}` : status;
|
|
|
3990
4819
|
}
|
|
3991
4820
|
}
|
|
3992
4821
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
3993
|
-
if (
|
|
4822
|
+
if (ingestEntrypoint) {
|
|
3994
4823
|
commandMessages.push(
|
|
3995
|
-
`Ingestion command: ${buildIngestCommand(worktree,
|
|
4824
|
+
`Ingestion command: ${buildIngestCommand(worktree, toolchain, ingestEntrypoint)}`
|
|
3996
4825
|
);
|
|
4826
|
+
if (toolchain.ingest.kind === "manual") {
|
|
4827
|
+
commandMessages.push(
|
|
4828
|
+
`The wizard does not run ${ingestionProfile.displayName} ${toolchain.packageManager.id} projects \u2014 run the command above yourself to ingest.`
|
|
4829
|
+
);
|
|
4830
|
+
}
|
|
3997
4831
|
}
|
|
3998
4832
|
await ctx.requestUserInput({
|
|
3999
4833
|
// No question being asked here, just an acknowledgement — the
|
|
@@ -4004,7 +4838,21 @@ ${run.output}` : status;
|
|
|
4004
4838
|
messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
|
|
4005
4839
|
});
|
|
4006
4840
|
}
|
|
4007
|
-
|
|
4841
|
+
const skipSearch = useCases.includes("search") && input.searchStrategy === "none";
|
|
4842
|
+
if (skipSearch) {
|
|
4843
|
+
const target = describeSearchTarget(
|
|
4844
|
+
input.searchStrategy,
|
|
4845
|
+
input.frameworkName
|
|
4846
|
+
);
|
|
4847
|
+
summaries.push(
|
|
4848
|
+
`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).`
|
|
4849
|
+
);
|
|
4850
|
+
ctx.setUserInput("implementation", "success");
|
|
4851
|
+
track("AI Wizard Search UI Skipped", {
|
|
4852
|
+
framework: input.frameworkName ?? "unknown"
|
|
4853
|
+
});
|
|
4854
|
+
}
|
|
4855
|
+
if (useCases.includes("search") && !skipSearch) {
|
|
4008
4856
|
let extraInstructions = [];
|
|
4009
4857
|
const preSearchFiles = new Set(await listChangedFiles(worktree));
|
|
4010
4858
|
for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
|
|
@@ -4077,7 +4925,7 @@ ${run.output}` : status;
|
|
|
4077
4925
|
}
|
|
4078
4926
|
if (installFailed) {
|
|
4079
4927
|
summaries.push(
|
|
4080
|
-
|
|
4928
|
+
`\u26A0\uFE0F Dependency install in the worktree failed. Install the ${ingestionProfile.displayName} dependencies in the worktree before the command below, or it will fail on a missing package.`
|
|
4081
4929
|
);
|
|
4082
4930
|
}
|
|
4083
4931
|
return {
|
|
@@ -4085,10 +4933,10 @@ ${run.output}` : status;
|
|
|
4085
4933
|
filesChanged,
|
|
4086
4934
|
summary: summaries.join("\n\n"),
|
|
4087
4935
|
worktreePath: worktree,
|
|
4088
|
-
...useCases.includes("ingestion") &&
|
|
4936
|
+
...useCases.includes("ingestion") && ingestEntrypoint ? {
|
|
4089
4937
|
ingestCommand: buildIngestCommand(
|
|
4090
4938
|
worktree,
|
|
4091
|
-
|
|
4939
|
+
toolchain,
|
|
4092
4940
|
ingestEntrypoint
|
|
4093
4941
|
),
|
|
4094
4942
|
ingestScriptRan,
|