@algolia/wizard 0.6.0-rc.53.27 → 0.6.0-rc.53.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +337 -1251
- package/docs/algolia-sdk/README.md +30 -53
- package/docs/algolia-sdk/search-single-index.md +42 -0
- package/package.json +1 -1
- package/docs/algolia-sdk/instantsearch-setup-templates.md +0 -92
- package/docs/algolia-sdk/save-records-csharp.md +0 -71
- package/docs/algolia-sdk/save-records-dart.md +0 -74
- package/docs/algolia-sdk/save-records-go.md +0 -62
- package/docs/algolia-sdk/save-records-java.md +0 -66
- package/docs/algolia-sdk/save-records-kotlin.md +0 -60
- package/docs/algolia-sdk/save-records-php.md +0 -50
- package/docs/algolia-sdk/save-records-python.md +0 -51
- package/docs/algolia-sdk/save-records-ruby.md +0 -48
- package/docs/algolia-sdk/save-records-scala.md +0 -68
- package/docs/algolia-sdk/save-records-swift.md +0 -88
package/dist/main.js
CHANGED
|
@@ -916,12 +916,12 @@ var sidebarItems = [
|
|
|
916
916
|
description: "push 100 records to Algolia in seconds"
|
|
917
917
|
},
|
|
918
918
|
{
|
|
919
|
-
title: "detect your
|
|
920
|
-
description: "React, Vue, Angular,
|
|
919
|
+
title: "detect your framework",
|
|
920
|
+
description: "React, Vue, Angular, Vanilla JS"
|
|
921
921
|
},
|
|
922
922
|
{
|
|
923
923
|
title: "scaffold a search UI",
|
|
924
|
-
description: "a styled InstantSearch
|
|
924
|
+
description: "a styled InstantSearch component, wired into your app"
|
|
925
925
|
},
|
|
926
926
|
{
|
|
927
927
|
title: "ship it",
|
|
@@ -1027,7 +1027,7 @@ var accessItems = [
|
|
|
1027
1027
|
{
|
|
1028
1028
|
tag: "READ",
|
|
1029
1029
|
title: "Project files",
|
|
1030
|
-
description: "reads
|
|
1030
|
+
description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
1031
1031
|
},
|
|
1032
1032
|
{
|
|
1033
1033
|
tag: "WRITE",
|
|
@@ -2446,719 +2446,15 @@ function writeCredentialsTool(ctx) {
|
|
|
2446
2446
|
// src/lib/tools/searchFiles.ts
|
|
2447
2447
|
import { tool as tool7 } from "ai";
|
|
2448
2448
|
import z12 from "zod";
|
|
2449
|
-
import { readdir as
|
|
2450
|
-
import { join as join9 } from "node:path";
|
|
2451
|
-
|
|
2452
|
-
// src/lib/languages.ts
|
|
2453
|
-
import { readdir as readdir2 } from "node:fs/promises";
|
|
2454
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
2455
|
-
import { join as join8 } from "node:path";
|
|
2456
|
-
|
|
2457
|
-
// src/lib/tools/utils/packageManager.ts
|
|
2458
|
-
import { readFile as readFile5 } from "node:fs/promises";
|
|
2459
|
-
import { existsSync } from "node:fs";
|
|
2449
|
+
import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
|
|
2460
2450
|
import { join as join7 } from "node:path";
|
|
2461
|
-
var LOCKFILES = [
|
|
2462
|
-
["pnpm-lock.yaml", "pnpm"],
|
|
2463
|
-
["yarn.lock", "yarn"],
|
|
2464
|
-
["bun.lockb", "bun"],
|
|
2465
|
-
["bun.lock", "bun"],
|
|
2466
|
-
["package-lock.json", "npm"]
|
|
2467
|
-
];
|
|
2468
|
-
async function readPackageJson(cwd = process.cwd()) {
|
|
2469
|
-
return JSON.parse(await readFile5(join7(cwd, "package.json"), "utf8"));
|
|
2470
|
-
}
|
|
2471
|
-
function packageManagerFrom(pkg) {
|
|
2472
|
-
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2473
|
-
}
|
|
2474
|
-
function packageManagerFromLockfile(cwd) {
|
|
2475
|
-
return LOCKFILES.find(([file]) => existsSync(join7(cwd, file)))?.[1];
|
|
2476
|
-
}
|
|
2477
|
-
async function detectPackageManager(cwd) {
|
|
2478
|
-
try {
|
|
2479
|
-
const pkg = await readPackageJson(cwd);
|
|
2480
|
-
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2481
|
-
} catch {
|
|
2482
|
-
}
|
|
2483
|
-
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2484
|
-
}
|
|
2485
|
-
|
|
2486
|
-
// src/lib/shell.ts
|
|
2487
|
-
function shellQuote(value) {
|
|
2488
|
-
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
2489
|
-
}
|
|
2490
|
-
|
|
2491
|
-
// src/lib/languages.ts
|
|
2492
|
-
var ENTRYPOINT_TOKEN = "{entrypoint}";
|
|
2493
|
-
var INGEST_DIR = ".algolia-wizard";
|
|
2494
|
-
var VISIBLE_INGEST_DIR = "algolia-wizard";
|
|
2495
|
-
var PY_VENV = `${INGEST_DIR}/.venv`;
|
|
2496
|
-
var PY_VENV_PYTHON = `${PY_VENV}/bin/python`;
|
|
2497
|
-
var PY_REQUIREMENTS = `${INGEST_DIR}/requirements.txt`;
|
|
2498
|
-
var CSHARP_PROJECT = `${INGEST_DIR}/ingest/ingest.csproj`;
|
|
2499
|
-
var SWIFT_PACKAGE_DIR = `${INGEST_DIR}/Ingest`;
|
|
2500
|
-
var LANGUAGE_PROFILES = {
|
|
2501
|
-
javascript: {
|
|
2502
|
-
id: "javascript",
|
|
2503
|
-
displayName: "JavaScript/TypeScript",
|
|
2504
|
-
aliases: [
|
|
2505
|
-
"javascript",
|
|
2506
|
-
"js",
|
|
2507
|
-
"typescript",
|
|
2508
|
-
"ts",
|
|
2509
|
-
"node",
|
|
2510
|
-
"nodejs",
|
|
2511
|
-
"node.js",
|
|
2512
|
-
"bun",
|
|
2513
|
-
"deno",
|
|
2514
|
-
"ecmascript",
|
|
2515
|
-
"jsx",
|
|
2516
|
-
"tsx"
|
|
2517
|
-
],
|
|
2518
|
-
manifests: ["package.json"],
|
|
2519
|
-
// The concrete npm-family manager is resolved by detectPackageManager (it
|
|
2520
|
-
// honours the package.json `packageManager` field, which lockfiles can't
|
|
2521
|
-
// express), so one spec covers all four and `resolveToolchain` rewrites the
|
|
2522
|
-
// binary below.
|
|
2523
|
-
packageManagers: [
|
|
2524
|
-
{
|
|
2525
|
-
id: "npm",
|
|
2526
|
-
dependency: { mode: "agent-declares", file: "package.json" },
|
|
2527
|
-
installSteps: [{ argv: ["npm", "install"] }],
|
|
2528
|
-
ingest: {
|
|
2529
|
-
kind: "auto",
|
|
2530
|
-
argv: ["node", ENTRYPOINT_TOKEN],
|
|
2531
|
-
entrypointExtensions: [".mjs", ".cjs", ".js"]
|
|
2532
|
-
}
|
|
2533
|
-
}
|
|
2534
|
-
],
|
|
2535
|
-
sdk: { packageName: "algoliasearch", versionPin: "^5", docKey: "js" },
|
|
2536
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.mjs`,
|
|
2537
|
-
// package.json scripts are repo-defined, so they're resolved at run time by
|
|
2538
|
-
// repoVerification rather than listed here.
|
|
2539
|
-
verification: [],
|
|
2540
|
-
envReadInstruction: "Read them from `process.env`.",
|
|
2541
|
-
skipDirs: ["node_modules", "dist", "build", "coverage", ".next", "out"]
|
|
2542
|
-
},
|
|
2543
|
-
python: {
|
|
2544
|
-
id: "python",
|
|
2545
|
-
displayName: "Python",
|
|
2546
|
-
aliases: ["python", "python3", "py", "cpython"],
|
|
2547
|
-
manifests: [
|
|
2548
|
-
"pyproject.toml",
|
|
2549
|
-
"requirements.txt",
|
|
2550
|
-
"setup.py",
|
|
2551
|
-
"setup.cfg",
|
|
2552
|
-
"Pipfile"
|
|
2553
|
-
],
|
|
2554
|
-
// Deliberately one path for every Python repo: a wizard-owned venv under
|
|
2555
|
-
// .algolia-wizard. Reusing the project's uv/poetry environment would mean
|
|
2556
|
-
// mutating the developer's real dependency manifest and lockfile, and the
|
|
2557
|
-
// declare-here/install-there split is the main way ingestion silently ends
|
|
2558
|
-
// up without the SDK installed. The tradeoff: the script can import the
|
|
2559
|
-
// Algolia client and anything it declares itself, but not the project's own
|
|
2560
|
-
// packages (see the optional root-requirements step below).
|
|
2561
|
-
packageManagers: [
|
|
2562
|
-
{
|
|
2563
|
-
id: "pip-venv",
|
|
2564
|
-
dependency: { mode: "agent-declares", file: PY_REQUIREMENTS },
|
|
2565
|
-
installSteps: [
|
|
2566
|
-
{ argv: ["python3", "-m", "venv", PY_VENV] },
|
|
2567
|
-
{
|
|
2568
|
-
argv: [PY_VENV_PYTHON, "-m", "pip", "install", "-r", PY_REQUIREMENTS]
|
|
2569
|
-
},
|
|
2570
|
-
// Best-effort access to the project's own dependencies (DB drivers,
|
|
2571
|
-
// ORMs) when the repo pins them the classic way.
|
|
2572
|
-
{
|
|
2573
|
-
argv: [
|
|
2574
|
-
PY_VENV_PYTHON,
|
|
2575
|
-
"-m",
|
|
2576
|
-
"pip",
|
|
2577
|
-
"install",
|
|
2578
|
-
"-r",
|
|
2579
|
-
"requirements.txt"
|
|
2580
|
-
],
|
|
2581
|
-
requiresFile: "requirements.txt",
|
|
2582
|
-
optional: true
|
|
2583
|
-
}
|
|
2584
|
-
],
|
|
2585
|
-
ingest: {
|
|
2586
|
-
kind: "auto",
|
|
2587
|
-
argv: [PY_VENV_PYTHON, ENTRYPOINT_TOKEN],
|
|
2588
|
-
entrypointExtensions: [".py"]
|
|
2589
|
-
}
|
|
2590
|
-
}
|
|
2591
|
-
],
|
|
2592
|
-
sdk: {
|
|
2593
|
-
packageName: "algoliasearch",
|
|
2594
|
-
versionPin: ">=4,<5",
|
|
2595
|
-
docKey: "python"
|
|
2596
|
-
},
|
|
2597
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.py`,
|
|
2598
|
-
verification: [
|
|
2599
|
-
{
|
|
2600
|
-
// -x skips the venv this same directory holds; without it the check
|
|
2601
|
-
// compiles every installed package instead of the generated script.
|
|
2602
|
-
label: "python compileall",
|
|
2603
|
-
argv: ["python3", "-m", "compileall", "-q", "-x", "[.]venv", INGEST_DIR],
|
|
2604
|
-
requiresFile: INGEST_DIR
|
|
2605
|
-
}
|
|
2606
|
-
],
|
|
2607
|
-
envReadInstruction: "Read them from `os.environ`.",
|
|
2608
|
-
skipDirs: [
|
|
2609
|
-
"venv",
|
|
2610
|
-
"__pycache__",
|
|
2611
|
-
"site-packages",
|
|
2612
|
-
"dist",
|
|
2613
|
-
"build",
|
|
2614
|
-
"htmlcov"
|
|
2615
|
-
]
|
|
2616
|
-
},
|
|
2617
|
-
ruby: {
|
|
2618
|
-
id: "ruby",
|
|
2619
|
-
displayName: "Ruby",
|
|
2620
|
-
aliases: ["ruby", "rb", "rails", "ruby on rails", "rubyonrails"],
|
|
2621
|
-
manifests: ["Gemfile", "*.gemspec"],
|
|
2622
|
-
packageManagers: [
|
|
2623
|
-
{
|
|
2624
|
-
id: "bundler",
|
|
2625
|
-
dependency: { mode: "agent-declares", file: "Gemfile" },
|
|
2626
|
-
installSteps: [{ argv: ["bundle", "install"] }],
|
|
2627
|
-
ingest: {
|
|
2628
|
-
kind: "auto",
|
|
2629
|
-
argv: ["bundle", "exec", "ruby", ENTRYPOINT_TOKEN],
|
|
2630
|
-
entrypointExtensions: [".rb"]
|
|
2631
|
-
}
|
|
2632
|
-
}
|
|
2633
|
-
],
|
|
2634
|
-
sdk: { packageName: "algolia", versionPin: "~> 3.0", docKey: "ruby" },
|
|
2635
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.rb`,
|
|
2636
|
-
// Ruby has no directory-level syntax check (`ruby -c` is one file at a
|
|
2637
|
-
// time), so verification relies on the agent's own review here.
|
|
2638
|
-
verification: [],
|
|
2639
|
-
envReadInstruction: "Read them from `ENV.fetch('NAME')`.",
|
|
2640
|
-
skipDirs: ["vendor", "tmp", "log", "coverage"]
|
|
2641
|
-
},
|
|
2642
|
-
php: {
|
|
2643
|
-
id: "php",
|
|
2644
|
-
displayName: "PHP",
|
|
2645
|
-
aliases: ["php", "laravel", "symfony"],
|
|
2646
|
-
manifests: ["composer.json"],
|
|
2647
|
-
packageManagers: [
|
|
2648
|
-
{
|
|
2649
|
-
id: "composer",
|
|
2650
|
-
// `composer require` both declares and installs, and unlike editing
|
|
2651
|
-
// composer.json by hand it can't leave composer.lock out of date (which
|
|
2652
|
-
// makes a later `composer install` refuse to run).
|
|
2653
|
-
dependency: { mode: "wizard-installs" },
|
|
2654
|
-
installSteps: [
|
|
2655
|
-
{
|
|
2656
|
-
argv: [
|
|
2657
|
-
"composer",
|
|
2658
|
-
"require",
|
|
2659
|
-
"algolia/algoliasearch-client-php:^4",
|
|
2660
|
-
"--no-interaction",
|
|
2661
|
-
// Repo post-install scripts are the project's code, not ours to
|
|
2662
|
-
// trigger; Laravel's package:discover also fails in a bare tree.
|
|
2663
|
-
"--no-scripts"
|
|
2664
|
-
]
|
|
2665
|
-
}
|
|
2666
|
-
],
|
|
2667
|
-
ingest: {
|
|
2668
|
-
kind: "auto",
|
|
2669
|
-
argv: ["php", ENTRYPOINT_TOKEN],
|
|
2670
|
-
entrypointExtensions: [".php"]
|
|
2671
|
-
}
|
|
2672
|
-
}
|
|
2673
|
-
],
|
|
2674
|
-
sdk: {
|
|
2675
|
-
packageName: "algolia/algoliasearch-client-php",
|
|
2676
|
-
versionPin: "^4",
|
|
2677
|
-
docKey: "php"
|
|
2678
|
-
},
|
|
2679
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.php`,
|
|
2680
|
-
verification: [],
|
|
2681
|
-
envReadInstruction: "Read them from `getenv('NAME')`.",
|
|
2682
|
-
skipDirs: ["vendor", "node_modules"]
|
|
2683
|
-
},
|
|
2684
|
-
go: {
|
|
2685
|
-
id: "go",
|
|
2686
|
-
displayName: "Go",
|
|
2687
|
-
aliases: ["go", "golang"],
|
|
2688
|
-
manifests: ["go.mod"],
|
|
2689
|
-
packageManagers: [
|
|
2690
|
-
{
|
|
2691
|
-
id: "gomod",
|
|
2692
|
-
// Imports in the generated file are the declaration; `go mod tidy`
|
|
2693
|
-
// resolves and fetches them — which only works because the script lives
|
|
2694
|
-
// outside INGEST_DIR (see VISIBLE_INGEST_DIR).
|
|
2695
|
-
dependency: { mode: "code-imports" },
|
|
2696
|
-
installSteps: [{ argv: ["go", "mod", "tidy"] }],
|
|
2697
|
-
ingest: {
|
|
2698
|
-
kind: "auto",
|
|
2699
|
-
argv: ["go", "run", ENTRYPOINT_TOKEN],
|
|
2700
|
-
entrypointExtensions: [".go"]
|
|
2701
|
-
}
|
|
2702
|
-
}
|
|
2703
|
-
],
|
|
2704
|
-
sdk: {
|
|
2705
|
-
packageName: "github.com/algolia/algoliasearch-client-go/v4",
|
|
2706
|
-
versionPin: "v4",
|
|
2707
|
-
docKey: "go"
|
|
2708
|
-
},
|
|
2709
|
-
ingestEntrypointExample: `${VISIBLE_INGEST_DIR}/ingest.go`,
|
|
2710
|
-
verification: [
|
|
2711
|
-
{ label: "go vet", argv: ["go", "vet", "./..."], requiresFile: "go.mod" }
|
|
2712
|
-
],
|
|
2713
|
-
envReadInstruction: "Read them from `os.Getenv`.",
|
|
2714
|
-
skipDirs: ["vendor", "bin"]
|
|
2715
|
-
},
|
|
2716
|
-
java: {
|
|
2717
|
-
id: "java",
|
|
2718
|
-
displayName: "Java",
|
|
2719
|
-
aliases: ["java"],
|
|
2720
|
-
manifests: ["pom.xml", "build.gradle", "build.gradle.kts"],
|
|
2721
|
-
packageManagers: [
|
|
2722
|
-
{
|
|
2723
|
-
id: "maven",
|
|
2724
|
-
detectFiles: ["pom.xml"],
|
|
2725
|
-
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2726
|
-
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2727
|
-
// The main class is a wizard constant the instructions require the agent
|
|
2728
|
-
// to use, so execution can't be redirected by agent output. Runnable only
|
|
2729
|
-
// because the install step above compiles src/main/java first — which is
|
|
2730
|
-
// why the entrypoint lives there rather than under .algolia-wizard/.
|
|
2731
|
-
ingest: {
|
|
2732
|
-
kind: "auto",
|
|
2733
|
-
argv: [
|
|
2734
|
-
"mvn",
|
|
2735
|
-
"-q",
|
|
2736
|
-
"org.codehaus.mojo:exec-maven-plugin:3.5.0:java",
|
|
2737
|
-
"-Dexec.mainClass=AlgoliaWizardIngest"
|
|
2738
|
-
],
|
|
2739
|
-
entrypointExtensions: [".java"]
|
|
2740
|
-
}
|
|
2741
|
-
},
|
|
2742
|
-
{
|
|
2743
|
-
id: "gradle",
|
|
2744
|
-
detectFiles: ["build.gradle", "build.gradle.kts"],
|
|
2745
|
-
dependency: {
|
|
2746
|
-
mode: "agent-declares",
|
|
2747
|
-
file: "build.gradle",
|
|
2748
|
-
alternatives: ["build.gradle.kts"]
|
|
2749
|
-
},
|
|
2750
|
-
installSteps: [],
|
|
2751
|
-
// Auto-running means executing the repo's own ./gradlew wrapper; out of
|
|
2752
|
-
// scope for now, so the wizard writes the code and prints the command.
|
|
2753
|
-
ingest: {
|
|
2754
|
-
kind: "manual",
|
|
2755
|
-
entrypointExtensions: [".java"],
|
|
2756
|
-
runCommand: "./gradlew runAlgoliaIngest"
|
|
2757
|
-
}
|
|
2758
|
-
}
|
|
2759
|
-
],
|
|
2760
|
-
sdk: {
|
|
2761
|
-
packageName: "com.algolia:algoliasearch",
|
|
2762
|
-
versionPin: "4.+",
|
|
2763
|
-
docKey: "java",
|
|
2764
|
-
alsoRequires: "The class must be named AlgoliaWizardIngest, in the default package (no `package` statement), with a `public static void main`."
|
|
2765
|
-
},
|
|
2766
|
-
// Not under .algolia-wizard/: Maven and Gradle only compile src/main/<lang>,
|
|
2767
|
-
// so a class outside it never makes it onto the classpath and the run command
|
|
2768
|
-
// fails with "class not found".
|
|
2769
|
-
ingestEntrypointExample: "src/main/java/AlgoliaWizardIngest.java",
|
|
2770
|
-
verification: [
|
|
2771
|
-
{
|
|
2772
|
-
label: "mvn compile",
|
|
2773
|
-
argv: ["mvn", "-q", "-DskipTests", "compile"],
|
|
2774
|
-
requiresFile: "pom.xml"
|
|
2775
|
-
}
|
|
2776
|
-
],
|
|
2777
|
-
envReadInstruction: "Read them from `System.getenv`.",
|
|
2778
|
-
skipDirs: ["target", "build", "out"]
|
|
2779
|
-
},
|
|
2780
|
-
kotlin: {
|
|
2781
|
-
id: "kotlin",
|
|
2782
|
-
displayName: "Kotlin",
|
|
2783
|
-
aliases: ["kotlin", "kt", "ktor"],
|
|
2784
|
-
manifests: ["build.gradle.kts", "build.gradle", "pom.xml"],
|
|
2785
|
-
packageManagers: [
|
|
2786
|
-
{
|
|
2787
|
-
id: "gradle",
|
|
2788
|
-
detectFiles: ["build.gradle.kts", "build.gradle"],
|
|
2789
|
-
dependency: {
|
|
2790
|
-
mode: "agent-declares",
|
|
2791
|
-
file: "build.gradle.kts",
|
|
2792
|
-
alternatives: ["build.gradle"]
|
|
2793
|
-
},
|
|
2794
|
-
installSteps: [],
|
|
2795
|
-
ingest: {
|
|
2796
|
-
kind: "manual",
|
|
2797
|
-
entrypointExtensions: [".kt"],
|
|
2798
|
-
runCommand: "./gradlew runAlgoliaIngest"
|
|
2799
|
-
}
|
|
2800
|
-
},
|
|
2801
|
-
// Kotlin/Maven is rare but real, and pom.xml is a Kotlin manifest — without
|
|
2802
|
-
// this spec such a repo falls through to Gradle and is told to run a
|
|
2803
|
-
// ./gradlew task that doesn't exist. Compiling needs the repo's own
|
|
2804
|
-
// kotlin-maven-plugin, so the run stays the developer's step.
|
|
2805
|
-
{
|
|
2806
|
-
id: "maven",
|
|
2807
|
-
detectFiles: ["pom.xml"],
|
|
2808
|
-
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2809
|
-
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2810
|
-
ingest: {
|
|
2811
|
-
kind: "manual",
|
|
2812
|
-
entrypointExtensions: [".kt"],
|
|
2813
|
-
runCommand: "mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java -Dexec.mainClass=AlgoliaWizardIngest"
|
|
2814
|
-
}
|
|
2815
|
-
}
|
|
2816
|
-
],
|
|
2817
|
-
sdk: {
|
|
2818
|
-
packageName: "com.algolia:algoliasearch-client-kotlin",
|
|
2819
|
-
versionPin: "3.+",
|
|
2820
|
-
docKey: "kotlin",
|
|
2821
|
-
// The published client's commonMain ships only ktor-client-core; without an
|
|
2822
|
-
// engine the script compiles and then fails at its first request.
|
|
2823
|
-
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."
|
|
2824
|
-
},
|
|
2825
|
-
ingestEntrypointExample: "src/main/kotlin/AlgoliaWizardIngest.kt",
|
|
2826
|
-
verification: [],
|
|
2827
|
-
envReadInstruction: "Read them from `System.getenv`.",
|
|
2828
|
-
skipDirs: ["build", "out"]
|
|
2829
|
-
},
|
|
2830
|
-
scala: {
|
|
2831
|
-
id: "scala",
|
|
2832
|
-
displayName: "Scala",
|
|
2833
|
-
aliases: ["scala", "sbt"],
|
|
2834
|
-
manifests: ["build.sbt", "build.sc"],
|
|
2835
|
-
packageManagers: [
|
|
2836
|
-
{
|
|
2837
|
-
id: "sbt",
|
|
2838
|
-
dependency: { mode: "agent-declares", file: "build.sbt" },
|
|
2839
|
-
installSteps: [],
|
|
2840
|
-
ingest: {
|
|
2841
|
-
kind: "manual",
|
|
2842
|
-
entrypointExtensions: [".scala"],
|
|
2843
|
-
runCommand: 'sbt "runMain AlgoliaWizardIngest"'
|
|
2844
|
-
}
|
|
2845
|
-
}
|
|
2846
|
-
],
|
|
2847
|
-
sdk: {
|
|
2848
|
-
packageName: "com.algolia:algoliasearch-scala_2.13",
|
|
2849
|
-
versionPin: "2.+",
|
|
2850
|
-
docKey: "scala",
|
|
2851
|
-
alsoRequires: "Name the object AlgoliaWizardIngest in the default package (no `package` statement) so `runMain AlgoliaWizardIngest` resolves it."
|
|
2852
|
-
},
|
|
2853
|
-
ingestEntrypointExample: "src/main/scala/AlgoliaWizardIngest.scala",
|
|
2854
|
-
verification: [],
|
|
2855
|
-
envReadInstruction: "Read them from `sys.env`.",
|
|
2856
|
-
// `project/` holds sbt's build definition, but the name is generic enough
|
|
2857
|
-
// that some repos use it for source; scanning it is cheap, missing source
|
|
2858
|
-
// is not.
|
|
2859
|
-
skipDirs: ["target"]
|
|
2860
|
-
},
|
|
2861
|
-
csharp: {
|
|
2862
|
-
id: "csharp",
|
|
2863
|
-
displayName: "C#",
|
|
2864
|
-
aliases: ["c#", "csharp", "cs", ".net", "dotnet", "net", "asp.net"],
|
|
2865
|
-
manifests: ["*.csproj", "*.sln", "global.json"],
|
|
2866
|
-
packageManagers: [
|
|
2867
|
-
{
|
|
2868
|
-
id: "dotnet",
|
|
2869
|
-
// A self-contained project under .algolia-wizard keeps the ingest script
|
|
2870
|
-
// out of the repo's own build graph.
|
|
2871
|
-
dependency: { mode: "agent-declares", file: CSHARP_PROJECT },
|
|
2872
|
-
installSteps: [{ argv: ["dotnet", "restore", CSHARP_PROJECT] }],
|
|
2873
|
-
ingest: {
|
|
2874
|
-
kind: "auto",
|
|
2875
|
-
argv: ["dotnet", "run", "--project", ENTRYPOINT_TOKEN],
|
|
2876
|
-
entrypointExtensions: [".csproj"]
|
|
2877
|
-
}
|
|
2878
|
-
}
|
|
2879
|
-
],
|
|
2880
|
-
sdk: {
|
|
2881
|
-
packageName: "Algolia.Search",
|
|
2882
|
-
versionPin: "7.*",
|
|
2883
|
-
docKey: "csharp"
|
|
2884
|
-
},
|
|
2885
|
-
ingestEntrypointExample: CSHARP_PROJECT,
|
|
2886
|
-
verification: [
|
|
2887
|
-
{
|
|
2888
|
-
label: "dotnet build",
|
|
2889
|
-
argv: ["dotnet", "build", CSHARP_PROJECT, "--nologo"],
|
|
2890
|
-
requiresFile: CSHARP_PROJECT
|
|
2891
|
-
}
|
|
2892
|
-
],
|
|
2893
|
-
envReadInstruction: 'Read them from `Environment.GetEnvironmentVariable("NAME")`.',
|
|
2894
|
-
// Deliberately not `packages`: modern .NET uses PackageReference, and
|
|
2895
|
-
// `packages/` is where pnpm/Lerna/Turborepo monorepos keep all their source —
|
|
2896
|
-
// skipping it would hide the entities the scan is looking for.
|
|
2897
|
-
skipDirs: ["bin", "obj"]
|
|
2898
|
-
},
|
|
2899
|
-
swift: {
|
|
2900
|
-
id: "swift",
|
|
2901
|
-
displayName: "Swift",
|
|
2902
|
-
aliases: ["swift", "swiftui", "ios", "vapor"],
|
|
2903
|
-
manifests: ["Package.swift", "*.xcodeproj", "*.xcworkspace"],
|
|
2904
|
-
packageManagers: [
|
|
2905
|
-
{
|
|
2906
|
-
id: "swiftpm",
|
|
2907
|
-
dependency: {
|
|
2908
|
-
mode: "agent-declares",
|
|
2909
|
-
file: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2910
|
-
},
|
|
2911
|
-
// `swift build` resolves and fetches; a cold build of the client is slow
|
|
2912
|
-
// (minutes), which is why the caller degrades to the manual command when
|
|
2913
|
-
// this fails.
|
|
2914
|
-
installSteps: [
|
|
2915
|
-
{
|
|
2916
|
-
argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2917
|
-
requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2918
|
-
}
|
|
2919
|
-
],
|
|
2920
|
-
ingest: {
|
|
2921
|
-
kind: "auto",
|
|
2922
|
-
argv: ["swift", "run", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2923
|
-
entrypointExtensions: [".swift"]
|
|
2924
|
-
}
|
|
2925
|
-
}
|
|
2926
|
-
],
|
|
2927
|
-
sdk: {
|
|
2928
|
-
packageName: "algoliasearch-client-swift",
|
|
2929
|
-
// SwiftPM range syntax, not an exact version — a bare "9.0.0" in a
|
|
2930
|
-
// Package.swift dependency pins the patch.
|
|
2931
|
-
versionPin: 'from: "9.0.0"',
|
|
2932
|
-
docKey: "swift"
|
|
2933
|
-
},
|
|
2934
|
-
ingestEntrypointExample: `${SWIFT_PACKAGE_DIR}/Sources/Ingest/main.swift`,
|
|
2935
|
-
verification: [
|
|
2936
|
-
{
|
|
2937
|
-
label: "swift build",
|
|
2938
|
-
argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2939
|
-
requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2940
|
-
}
|
|
2941
|
-
],
|
|
2942
|
-
envReadInstruction: "Read them from `ProcessInfo.processInfo.environment`.",
|
|
2943
|
-
skipDirs: ["Pods", "DerivedData", "Carthage", ".build"]
|
|
2944
|
-
},
|
|
2945
|
-
dart: {
|
|
2946
|
-
id: "dart",
|
|
2947
|
-
displayName: "Dart",
|
|
2948
|
-
aliases: ["dart", "flutter"],
|
|
2949
|
-
manifests: ["pubspec.yaml"],
|
|
2950
|
-
packageManagers: [
|
|
2951
|
-
{
|
|
2952
|
-
id: "flutter-pub",
|
|
2953
|
-
detectFiles: [".metadata"],
|
|
2954
|
-
dependency: { mode: "agent-declares", file: "pubspec.yaml" },
|
|
2955
|
-
installSteps: [{ argv: ["flutter", "pub", "get"] }],
|
|
2956
|
-
ingest: {
|
|
2957
|
-
kind: "auto",
|
|
2958
|
-
argv: ["dart", "run", ENTRYPOINT_TOKEN],
|
|
2959
|
-
entrypointExtensions: [".dart"]
|
|
2960
|
-
}
|
|
2961
|
-
},
|
|
2962
|
-
{
|
|
2963
|
-
id: "pub",
|
|
2964
|
-
dependency: { mode: "agent-declares", file: "pubspec.yaml" },
|
|
2965
|
-
installSteps: [{ argv: ["dart", "pub", "get"] }],
|
|
2966
|
-
ingest: {
|
|
2967
|
-
kind: "auto",
|
|
2968
|
-
argv: ["dart", "run", ENTRYPOINT_TOKEN],
|
|
2969
|
-
entrypointExtensions: [".dart"]
|
|
2970
|
-
}
|
|
2971
|
-
}
|
|
2972
|
-
],
|
|
2973
|
-
sdk: {
|
|
2974
|
-
packageName: "algolia_client_search",
|
|
2975
|
-
versionPin: "^1.0.0",
|
|
2976
|
-
docKey: "dart"
|
|
2977
|
-
},
|
|
2978
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.dart`,
|
|
2979
|
-
verification: [
|
|
2980
|
-
{
|
|
2981
|
-
// Gated on the directory it analyzes, not just pubspec.yaml: a run that
|
|
2982
|
-
// only built a search UI never created it, and `dart analyze` on a
|
|
2983
|
-
// missing path fails the whole verification pass.
|
|
2984
|
-
label: "dart analyze",
|
|
2985
|
-
argv: ["dart", "analyze", INGEST_DIR],
|
|
2986
|
-
requiresFile: INGEST_DIR
|
|
2987
|
-
}
|
|
2988
|
-
],
|
|
2989
|
-
envReadInstruction: "Read them from `Platform.environment`.",
|
|
2990
|
-
skipDirs: ["build"]
|
|
2991
|
-
}
|
|
2992
|
-
};
|
|
2993
|
-
var DEFAULT_LANGUAGE_ID = "javascript";
|
|
2994
|
-
var JAVASCRIPT = "javascript";
|
|
2995
|
-
var CURATED_LANGUAGES = Object.values(
|
|
2996
|
-
LANGUAGE_PROFILES
|
|
2997
|
-
).map((profile) => profile.displayName);
|
|
2998
|
-
function isBackendLanguage(profile) {
|
|
2999
|
-
return profile.id !== JAVASCRIPT;
|
|
3000
|
-
}
|
|
3001
|
-
function normalizeLanguageName(name) {
|
|
3002
|
-
return name.toLowerCase().trim().replace(/[\s_-]+/g, "");
|
|
3003
|
-
}
|
|
3004
|
-
var ALIAS_TO_ID = /* @__PURE__ */ new Map();
|
|
3005
|
-
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
3006
|
-
for (const alias of [profile.id, profile.displayName, ...profile.aliases]) {
|
|
3007
|
-
ALIAS_TO_ID.set(normalizeLanguageName(alias), profile.id);
|
|
3008
|
-
}
|
|
3009
|
-
}
|
|
3010
|
-
function resolveLanguageProfile(name) {
|
|
3011
|
-
const id = ALIAS_TO_ID.get(normalizeLanguageName(name));
|
|
3012
|
-
return id ? LANGUAGE_PROFILES[id] : void 0;
|
|
3013
|
-
}
|
|
3014
|
-
function isSameLanguage(a, b) {
|
|
3015
|
-
const x = resolveLanguageProfile(a);
|
|
3016
|
-
const y = resolveLanguageProfile(b);
|
|
3017
|
-
if (x && y) return x.id === y.id;
|
|
3018
|
-
if (x || y) return false;
|
|
3019
|
-
const folded = normalizeLanguageName(a);
|
|
3020
|
-
return folded !== "" && folded === normalizeLanguageName(b);
|
|
3021
|
-
}
|
|
3022
|
-
var BASE_SKIP_DIRS = ["node_modules", ".git", "dist"];
|
|
3023
|
-
var ALL_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
3024
|
-
...BASE_SKIP_DIRS,
|
|
3025
|
-
...Object.values(LANGUAGE_PROFILES).flatMap((p) => p.skipDirs)
|
|
3026
|
-
]);
|
|
3027
|
-
var ALLOWED_BINARIES = new Set(
|
|
3028
|
-
Object.values(LANGUAGE_PROFILES).flatMap((profile) => [
|
|
3029
|
-
...profile.packageManagers.flatMap((pm) => [
|
|
3030
|
-
...pm.installSteps.map((s) => s.argv[0]),
|
|
3031
|
-
...pm.ingest.kind === "auto" ? [pm.ingest.argv[0]] : []
|
|
3032
|
-
]),
|
|
3033
|
-
...profile.verification.map((v) => v.argv[0])
|
|
3034
|
-
])
|
|
3035
|
-
);
|
|
3036
|
-
var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
|
|
3037
|
-
function isWorktreeRelativeCommand(command) {
|
|
3038
|
-
return command.includes("/");
|
|
3039
|
-
}
|
|
3040
|
-
function withCommand(argv, command) {
|
|
3041
|
-
return [command, ...argv.slice(1)];
|
|
3042
|
-
}
|
|
3043
|
-
function resolveDeclaredManifest(root, packageManager) {
|
|
3044
|
-
const { dependency } = packageManager;
|
|
3045
|
-
if (dependency.mode !== "agent-declares" || !dependency.alternatives?.length) {
|
|
3046
|
-
return packageManager;
|
|
3047
|
-
}
|
|
3048
|
-
const present = [dependency.file, ...dependency.alternatives].find(
|
|
3049
|
-
(file) => existsSync2(join8(root, file))
|
|
3050
|
-
);
|
|
3051
|
-
if (!present || present === dependency.file) return packageManager;
|
|
3052
|
-
return { ...packageManager, dependency: { ...dependency, file: present } };
|
|
3053
|
-
}
|
|
3054
|
-
async function manifestPresent(root, manifest, listing) {
|
|
3055
|
-
if (!manifest.startsWith("*.")) return existsSync2(join8(root, manifest));
|
|
3056
|
-
if (!listing.entries) {
|
|
3057
|
-
const entries = await readdir2(root).catch(() => []);
|
|
3058
|
-
listing.entries = Array.isArray(entries) ? entries : [];
|
|
3059
|
-
}
|
|
3060
|
-
const suffix = manifest.slice(1);
|
|
3061
|
-
return listing.entries.some((e) => e.endsWith(suffix));
|
|
3062
|
-
}
|
|
3063
|
-
async function profileManifestPresent(root, profile, listing) {
|
|
3064
|
-
for (const manifest of profile.manifests) {
|
|
3065
|
-
if (await manifestPresent(root, manifest, listing)) return true;
|
|
3066
|
-
}
|
|
3067
|
-
return false;
|
|
3068
|
-
}
|
|
3069
|
-
async function detectProfilesFromManifests(root) {
|
|
3070
|
-
const listing = {};
|
|
3071
|
-
const found = [];
|
|
3072
|
-
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
3073
|
-
if (await profileManifestPresent(root, profile, listing)) found.push(profile);
|
|
3074
|
-
}
|
|
3075
|
-
return found;
|
|
3076
|
-
}
|
|
3077
|
-
async function hasProfileManifest(root, profile) {
|
|
3078
|
-
return profileManifestPresent(root, profile, {});
|
|
3079
|
-
}
|
|
3080
|
-
async function pickIngestionCandidates(root, confirmedNames) {
|
|
3081
|
-
const confirmed3 = confirmedNames.map((name) => resolveLanguageProfile(name)).filter((p) => p !== void 0);
|
|
3082
|
-
const onDisk = await detectProfilesFromManifests(root);
|
|
3083
|
-
const onDiskIds = new Set(onDisk.map((p) => p.id));
|
|
3084
|
-
const candidates = [
|
|
3085
|
-
...new Map(
|
|
3086
|
-
confirmed3.filter((p) => onDiskIds.has(p.id)).map((p) => [p.id, p])
|
|
3087
|
-
).values()
|
|
3088
|
-
];
|
|
3089
|
-
return { candidates, confirmed: confirmed3, onDisk };
|
|
3090
|
-
}
|
|
3091
|
-
async function resolveToolchain(root, profile) {
|
|
3092
|
-
const matched = profile.packageManagers.find(
|
|
3093
|
-
(pm) => [...pm.lockfiles ?? [], ...pm.detectFiles ?? []].some(
|
|
3094
|
-
(f) => existsSync2(join8(root, f))
|
|
3095
|
-
)
|
|
3096
|
-
);
|
|
3097
|
-
const packageManager = resolveDeclaredManifest(
|
|
3098
|
-
root,
|
|
3099
|
-
matched ?? profile.packageManagers[0]
|
|
3100
|
-
);
|
|
3101
|
-
let { installSteps, ingest } = packageManager;
|
|
3102
|
-
installSteps = installSteps.map(
|
|
3103
|
-
(step) => isWorktreeRelativeCommand(step.argv[0]) ? { ...step, argv: withCommand(step.argv, join8(root, step.argv[0])) } : step
|
|
3104
|
-
);
|
|
3105
|
-
if (ingest.kind === "auto" && isWorktreeRelativeCommand(ingest.argv[0])) {
|
|
3106
|
-
ingest = {
|
|
3107
|
-
...ingest,
|
|
3108
|
-
argv: withCommand(ingest.argv, join8(root, ingest.argv[0]))
|
|
3109
|
-
};
|
|
3110
|
-
}
|
|
3111
|
-
if (profile.id === "javascript") {
|
|
3112
|
-
const pm = await detectPackageManager(root);
|
|
3113
|
-
if (JS_PACKAGE_MANAGERS.has(pm)) {
|
|
3114
|
-
installSteps = installSteps.map((step) => ({
|
|
3115
|
-
...step,
|
|
3116
|
-
argv: withCommand(step.argv, pm)
|
|
3117
|
-
}));
|
|
3118
|
-
if (pm === "bun" && ingest.kind === "auto") {
|
|
3119
|
-
ingest = { ...ingest, argv: withCommand(ingest.argv, "bun") };
|
|
3120
|
-
}
|
|
3121
|
-
}
|
|
3122
|
-
}
|
|
3123
|
-
return { profile, packageManager, installSteps, ingest };
|
|
3124
|
-
}
|
|
3125
|
-
function resolveIngestArgv(ingest, entrypoint) {
|
|
3126
|
-
if (ingest.kind !== "auto") {
|
|
3127
|
-
throw new Error("resolveIngestArgv called for a manual-run toolchain");
|
|
3128
|
-
}
|
|
3129
|
-
return ingest.argv.map(
|
|
3130
|
-
(part) => part === ENTRYPOINT_TOKEN ? entrypoint : part
|
|
3131
|
-
);
|
|
3132
|
-
}
|
|
3133
|
-
function describeIngestCommand(ingest, entrypoint) {
|
|
3134
|
-
if (ingest.kind !== "auto") return ingest.runCommand;
|
|
3135
|
-
return ingest.argv.map((part) => part === ENTRYPOINT_TOKEN ? shellQuote(entrypoint) : part).join(" ");
|
|
3136
|
-
}
|
|
3137
|
-
function ingestScriptDir(profile) {
|
|
3138
|
-
const parts = profile.ingestEntrypointExample.split("/");
|
|
3139
|
-
return parts.slice(0, -1).join("/") || ".";
|
|
3140
|
-
}
|
|
3141
|
-
function dependencyInstruction(toolchain) {
|
|
3142
|
-
const { profile, packageManager } = toolchain;
|
|
3143
|
-
const { packageName, versionPin } = profile.sdk;
|
|
3144
|
-
const also = profile.sdk.alsoRequires ? ` ${profile.sdk.alsoRequires}` : "";
|
|
3145
|
-
switch (packageManager.dependency.mode) {
|
|
3146
|
-
case "wizard-installs":
|
|
3147
|
-
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}`;
|
|
3148
|
-
case "code-imports":
|
|
3149
|
-
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}`;
|
|
3150
|
-
case "agent-declares":
|
|
3151
|
-
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}`;
|
|
3152
|
-
}
|
|
3153
|
-
}
|
|
3154
|
-
|
|
3155
|
-
// src/lib/tools/searchFiles.ts
|
|
3156
2451
|
var MAX_QUERY_LENGTH = 1e3;
|
|
3157
2452
|
async function walkFiles(dir) {
|
|
2453
|
+
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
3158
2454
|
const out = [];
|
|
3159
|
-
for (const e of await
|
|
3160
|
-
if (e.name.startsWith(".") ||
|
|
3161
|
-
const full =
|
|
2455
|
+
for (const e of await readdir2(dir, { withFileTypes: true })) {
|
|
2456
|
+
if (e.name.startsWith(".") || skip.has(e.name)) continue;
|
|
2457
|
+
const full = join7(dir, e.name);
|
|
3162
2458
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
3163
2459
|
else if (e.isFile()) out.push(full);
|
|
3164
2460
|
}
|
|
@@ -3191,7 +2487,7 @@ function searchFilesTool(ctx) {
|
|
|
3191
2487
|
for (const file of await walkFiles(resolved.target)) {
|
|
3192
2488
|
let content;
|
|
3193
2489
|
try {
|
|
3194
|
-
content = await
|
|
2490
|
+
content = await readFile5(file, "utf8");
|
|
3195
2491
|
} catch {
|
|
3196
2492
|
continue;
|
|
3197
2493
|
}
|
|
@@ -3215,146 +2511,88 @@ function searchFilesTool(ctx) {
|
|
|
3215
2511
|
import { tool as tool8 } from "ai";
|
|
3216
2512
|
import z13 from "zod";
|
|
3217
2513
|
|
|
3218
|
-
// src/lib/tools/repoVerification.ts
|
|
3219
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
3220
|
-
import { join as join10 } from "node:path";
|
|
3221
|
-
|
|
3222
2514
|
// src/lib/tools/utils/runCommand.ts
|
|
3223
2515
|
import { spawn as spawn2 } from "node:child_process";
|
|
3224
|
-
|
|
3225
|
-
var INGEST_TIMEOUT_MS = 15 * 6e4;
|
|
3226
|
-
var VERIFY_TIMEOUT_MS = 10 * 6e4;
|
|
3227
|
-
var KILL_GRACE_MS = 5e3;
|
|
3228
|
-
function runCommand(command, args, options = {}) {
|
|
3229
|
-
const { cwd, env, timeoutMs = VERIFY_TIMEOUT_MS } = options;
|
|
2516
|
+
function runCommand(command, args, cwd) {
|
|
3230
2517
|
return new Promise((resolve4) => {
|
|
3231
2518
|
let output = "";
|
|
3232
|
-
let settled = false;
|
|
3233
2519
|
const child = spawn2(command, args, {
|
|
3234
2520
|
cwd,
|
|
3235
|
-
|
|
3236
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
3237
|
-
...env ? { env: { ...process.env, ...env } } : {}
|
|
2521
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3238
2522
|
});
|
|
3239
|
-
const settle = (result) => {
|
|
3240
|
-
if (settled) return;
|
|
3241
|
-
settled = true;
|
|
3242
|
-
clearTimeout(timer);
|
|
3243
|
-
resolve4(result);
|
|
3244
|
-
};
|
|
3245
|
-
const timer = setTimeout(() => {
|
|
3246
|
-
child.kill("SIGTERM");
|
|
3247
|
-
setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
|
|
3248
|
-
const seconds = Math.round(timeoutMs / 1e3);
|
|
3249
|
-
settle({
|
|
3250
|
-
code: 1,
|
|
3251
|
-
output: `${output}
|
|
3252
|
-
Timed out after ${seconds}s: ${command} ${args.join(" ")}`.trim(),
|
|
3253
|
-
timedOut: true
|
|
3254
|
-
});
|
|
3255
|
-
}, timeoutMs);
|
|
3256
2523
|
child.stdout?.on("data", (d) => output += d);
|
|
3257
2524
|
child.stderr?.on("data", (d) => output += d);
|
|
3258
2525
|
child.on(
|
|
3259
2526
|
"error",
|
|
3260
|
-
(err) =>
|
|
3261
|
-
code: 1,
|
|
3262
|
-
output: `Failed to run ${command}: ${err.message}`,
|
|
3263
|
-
timedOut: false
|
|
3264
|
-
})
|
|
3265
|
-
);
|
|
3266
|
-
child.on(
|
|
3267
|
-
"close",
|
|
3268
|
-
(code) => settle({ code: code ?? 1, output, timedOut: false })
|
|
2527
|
+
(err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
|
|
3269
2528
|
);
|
|
2529
|
+
child.on("close", (code) => resolve4({ code: code ?? 1, output }));
|
|
3270
2530
|
});
|
|
3271
2531
|
}
|
|
3272
2532
|
|
|
2533
|
+
// src/lib/tools/utils/packageManager.ts
|
|
2534
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
2535
|
+
import { existsSync } from "node:fs";
|
|
2536
|
+
import { join as join8 } from "node:path";
|
|
2537
|
+
var LOCKFILES = [
|
|
2538
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
2539
|
+
["yarn.lock", "yarn"],
|
|
2540
|
+
["bun.lockb", "bun"],
|
|
2541
|
+
["bun.lock", "bun"],
|
|
2542
|
+
["package-lock.json", "npm"]
|
|
2543
|
+
];
|
|
2544
|
+
async function readPackageJson(cwd = process.cwd()) {
|
|
2545
|
+
return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
|
|
2546
|
+
}
|
|
2547
|
+
function packageManagerFrom(pkg) {
|
|
2548
|
+
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2549
|
+
}
|
|
2550
|
+
function packageManagerFromLockfile(cwd) {
|
|
2551
|
+
return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
|
|
2552
|
+
}
|
|
2553
|
+
async function detectPackageManager(cwd) {
|
|
2554
|
+
try {
|
|
2555
|
+
const pkg = await readPackageJson(cwd);
|
|
2556
|
+
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2557
|
+
} catch {
|
|
2558
|
+
}
|
|
2559
|
+
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2560
|
+
}
|
|
2561
|
+
|
|
3273
2562
|
// src/lib/tools/repoVerification.ts
|
|
3274
2563
|
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
3275
|
-
async function
|
|
3276
|
-
const { code, output } = await runCommand(binary, args, {
|
|
3277
|
-
timeoutMs: VERIFY_TIMEOUT_MS
|
|
3278
|
-
});
|
|
3279
|
-
return { command, exitCode: code, ok: code === 0, output: output.trim() };
|
|
3280
|
-
}
|
|
3281
|
-
async function javascriptChecks() {
|
|
2564
|
+
async function runRepoVerificationCheck() {
|
|
3282
2565
|
let pkg;
|
|
3283
2566
|
try {
|
|
3284
2567
|
pkg = await readPackageJson();
|
|
3285
2568
|
} catch (err) {
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
};
|
|
2569
|
+
const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
|
|
2570
|
+
return { ok: false, checks: [], limitation };
|
|
3289
2571
|
}
|
|
3290
2572
|
const scripts = pkg.scripts ?? {};
|
|
3291
2573
|
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
3292
2574
|
if (present.length === 0) {
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
};
|
|
2575
|
+
const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
|
|
2576
|
+
return { ok: false, checks: [], limitation };
|
|
3296
2577
|
}
|
|
3297
2578
|
const pm = await detectPackageManager(process.cwd());
|
|
3298
2579
|
const checks = [];
|
|
3299
2580
|
for (const script of present) {
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
);
|
|
3303
|
-
}
|
|
3304
|
-
return { checks };
|
|
3305
|
-
}
|
|
3306
|
-
async function runRepoVerificationCheck(languages = [DEFAULT_LANGUAGE_ID]) {
|
|
3307
|
-
const ids = [...new Set(languages)];
|
|
3308
|
-
if (ids.length === 0) ids.push(DEFAULT_LANGUAGE_ID);
|
|
3309
|
-
const checks = [];
|
|
3310
|
-
const limitations = [];
|
|
3311
|
-
for (const id of ids) {
|
|
3312
|
-
if (id === JAVASCRIPT) {
|
|
3313
|
-
const result = await javascriptChecks();
|
|
3314
|
-
if ("checks" in result) checks.push(...result.checks);
|
|
3315
|
-
else limitations.push(result.limitation);
|
|
3316
|
-
continue;
|
|
3317
|
-
}
|
|
3318
|
-
const profile = LANGUAGE_PROFILES[id];
|
|
3319
|
-
const runnable = profile.verification.filter(
|
|
3320
|
-
(spec) => !spec.requiresFile || existsSync3(join10(process.cwd(), spec.requiresFile))
|
|
3321
|
-
);
|
|
3322
|
-
if (runnable.length === 0) {
|
|
3323
|
-
limitations.push(
|
|
3324
|
-
`No mechanical verification available for ${profile.displayName} in this repo.`
|
|
3325
|
-
);
|
|
3326
|
-
continue;
|
|
3327
|
-
}
|
|
3328
|
-
for (const spec of runnable) {
|
|
3329
|
-
checks.push(
|
|
3330
|
-
await runCheck(spec.argv.join(" "), spec.argv[0], [
|
|
3331
|
-
...spec.argv.slice(1)
|
|
3332
|
-
])
|
|
3333
|
-
);
|
|
3334
|
-
}
|
|
2581
|
+
const command = `${pm} run ${script}`;
|
|
2582
|
+
const { code, output } = await runCommand(pm, ["run", script]);
|
|
2583
|
+
checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
|
|
3335
2584
|
}
|
|
3336
|
-
|
|
3337
|
-
return {
|
|
3338
|
-
ok: false,
|
|
3339
|
-
checks: [],
|
|
3340
|
-
limitation: limitations.join(" ") || "No verification checks available."
|
|
3341
|
-
};
|
|
3342
|
-
}
|
|
3343
|
-
return {
|
|
3344
|
-
ok: checks.every((c) => c.ok),
|
|
3345
|
-
checks,
|
|
3346
|
-
...limitations.length ? { limitation: limitations.join(" ") } : {}
|
|
3347
|
-
};
|
|
2585
|
+
return { ok: checks.every((c) => c.ok), checks };
|
|
3348
2586
|
}
|
|
3349
2587
|
|
|
3350
2588
|
// src/lib/tools/verifyImplementation.ts
|
|
3351
|
-
function verifyImplementationTool(
|
|
2589
|
+
function verifyImplementationTool() {
|
|
3352
2590
|
return tool8({
|
|
3353
|
-
description: "Run the repo's mechanical verification
|
|
2591
|
+
description: "Run the repo's mechanical verification check for generated implementation changes. Detects lint/typecheck/check from package.json and returns structured pass/fail evidence for the verifier to interpret.",
|
|
3354
2592
|
inputSchema: z13.object(),
|
|
3355
2593
|
execute: async () => {
|
|
3356
|
-
logger.info(
|
|
3357
|
-
return runRepoVerificationCheck(
|
|
2594
|
+
logger.info("called verifyImplementation tool");
|
|
2595
|
+
return runRepoVerificationCheck();
|
|
3358
2596
|
}
|
|
3359
2597
|
});
|
|
3360
2598
|
}
|
|
@@ -3480,17 +2718,12 @@ var DEFAULT_TOOL_LIMITS = {
|
|
|
3480
2718
|
read: 20,
|
|
3481
2719
|
match: 100
|
|
3482
2720
|
};
|
|
3483
|
-
function createToolContext({
|
|
3484
|
-
limits = DEFAULT_TOOL_LIMITS,
|
|
3485
|
-
cwd = process.cwd(),
|
|
3486
|
-
languages = [DEFAULT_LANGUAGE_ID]
|
|
3487
|
-
} = {}) {
|
|
2721
|
+
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
|
|
3488
2722
|
return {
|
|
3489
2723
|
root: cwd,
|
|
3490
2724
|
cwd,
|
|
3491
2725
|
limits,
|
|
3492
|
-
counts: { list: 0, search: 0, read: 0 }
|
|
3493
|
-
languages: languages.length ? languages : [DEFAULT_LANGUAGE_ID]
|
|
2726
|
+
counts: { list: 0, search: 0, read: 0 }
|
|
3494
2727
|
};
|
|
3495
2728
|
}
|
|
3496
2729
|
|
|
@@ -3527,7 +2760,7 @@ function createTools(ctx, { output, tools }) {
|
|
|
3527
2760
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
3528
2761
|
verifyImplementation: withLogging(
|
|
3529
2762
|
"verifyImplementation",
|
|
3530
|
-
verifyImplementationTool(
|
|
2763
|
+
verifyImplementationTool()
|
|
3531
2764
|
),
|
|
3532
2765
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
3533
2766
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
@@ -3563,7 +2796,7 @@ async function runAgent(req) {
|
|
|
3563
2796
|
baseURL: PROXY_BASE_URL,
|
|
3564
2797
|
fetch: proxyFetch
|
|
3565
2798
|
});
|
|
3566
|
-
const toolContext = createToolContext(
|
|
2799
|
+
const toolContext = createToolContext();
|
|
3567
2800
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
3568
2801
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
3569
2802
|
const instructions = [
|
|
@@ -3654,11 +2887,8 @@ var detectLanguageSchema = z18.object({
|
|
|
3654
2887
|
var detectLanguage = () => runAgent({
|
|
3655
2888
|
instructions: [
|
|
3656
2889
|
"Analyze the codebase and determine the programming languages and frameworks used",
|
|
3657
|
-
"
|
|
3658
|
-
"List the language that owns the backend/data code first \u2014 that is the one an ingestion script will be written in.",
|
|
3659
|
-
"If a superset language is found, exclude the subset language. TS-over-JS. Kotlin-over-Java when Kotlin is primary.",
|
|
2890
|
+
"If a superset language is found, exclude the subset language. TS-over-JS.",
|
|
3660
2891
|
"If a meta-framework is used, exclude the framework. Next-over-React.",
|
|
3661
|
-
"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).",
|
|
3662
2892
|
"Return the exact version",
|
|
3663
2893
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
3664
2894
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
@@ -3707,7 +2937,6 @@ var MODE_CONFIG = {
|
|
|
3707
2937
|
"Analyze the codebase to find the data entities (models) that should be ingested into Algolia.",
|
|
3708
2938
|
"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).",
|
|
3709
2939
|
"Inspect the source of each entity to extract real field names for attributes \u2014 do not guess or leave attributes empty.",
|
|
3710
|
-
"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.",
|
|
3711
2940
|
"Prefer domain models (e.g. Document, Product, User) over framework or infrastructure types.",
|
|
3712
2941
|
"Use as few tools as possible, but do not guess. If you cannot find any entities, return an empty array.",
|
|
3713
2942
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
@@ -3719,9 +2948,8 @@ var MODE_CONFIG = {
|
|
|
3719
2948
|
instructions: [
|
|
3720
2949
|
"Analyze the codebase to determine the single best location to add search UI functionality.",
|
|
3721
2950
|
"Prefer a shared, always-rendered layout location (e.g. a header or navigation component) so search is reachable across the app.",
|
|
3722
|
-
"
|
|
3723
|
-
|
|
3724
|
-
'Use as few tools as possible, but do not guess. If the project renders no UI at all (an API-only service), say "unknown".',
|
|
2951
|
+
"Return one file path as searchImplementationAnalysis (e.g. /layouts/header.tsx).",
|
|
2952
|
+
'Use as few tools as possible, but do not guess. If you cannot find a clear location, say "unknown".',
|
|
3725
2953
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
3726
2954
|
"When done, call reportStatus"
|
|
3727
2955
|
],
|
|
@@ -3730,8 +2958,8 @@ var MODE_CONFIG = {
|
|
|
3730
2958
|
verification: {
|
|
3731
2959
|
instructions: [
|
|
3732
2960
|
"Analyze the codebase to determine which code-quality tools are available to validate changes.",
|
|
3733
|
-
"Look at
|
|
3734
|
-
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"]
|
|
2961
|
+
"Look at package.json scripts, config files (e.g. .eslintrc, tsconfig, prettier), and dev dependencies.",
|
|
2962
|
+
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"].',
|
|
3735
2963
|
"Use as few tools as possible, but do not guess. If you cannot find any, return an empty array.",
|
|
3736
2964
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
3737
2965
|
"When done, call reportStatus"
|
|
@@ -3758,7 +2986,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3758
2986
|
// package.json
|
|
3759
2987
|
var package_default = {
|
|
3760
2988
|
name: "@algolia/wizard",
|
|
3761
|
-
version: "0.6.0-rc.53.
|
|
2989
|
+
version: "0.6.0-rc.53.32",
|
|
3762
2990
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3763
2991
|
type: "module",
|
|
3764
2992
|
engines: {
|
|
@@ -3859,185 +3087,82 @@ function parseEntries(raw) {
|
|
|
3859
3087
|
return raw.split(",").map(clean).filter(Boolean).slice(0, MAX_ENTRIES).map((name) => ({ name, version: "unknown" }));
|
|
3860
3088
|
}
|
|
3861
3089
|
var summarize = (entries) => entries.length ? entries.map((e) => e.name).join(", ") : "none";
|
|
3862
|
-
|
|
3863
|
-
// src/actions/confirmLanguage.ts
|
|
3864
|
-
import z21 from "zod";
|
|
3865
|
-
var confirmLanguageSchema = z21.object({
|
|
3866
|
-
languages: detectLanguageSchema.shape.languages
|
|
3867
|
-
});
|
|
3868
|
-
var OTHER_OPTION = "Other";
|
|
3869
|
-
function confirmed(languages) {
|
|
3870
|
-
track("AI Wizard Language Confirmed", { languages });
|
|
3871
|
-
return { languages };
|
|
3872
|
-
}
|
|
3873
|
-
async function askOtherLanguage(ctx) {
|
|
3874
|
-
let prompt = "enter the language for your ingestion script";
|
|
3090
|
+
async function askList(ctx, prompt, { required = false } = {}) {
|
|
3875
3091
|
for (; ; ) {
|
|
3876
3092
|
const answer = await ctx.requestUserInput({
|
|
3877
3093
|
prompt,
|
|
3878
3094
|
promptType: "textInput",
|
|
3879
|
-
options: []
|
|
3095
|
+
options: [],
|
|
3096
|
+
helpText: 'Comma-separated, e.g. "TypeScript, Node".'
|
|
3880
3097
|
});
|
|
3881
3098
|
if (typeof answer !== "string") {
|
|
3882
|
-
throw new Error("
|
|
3099
|
+
throw new Error("askList received an unexpected non-text result");
|
|
3883
3100
|
}
|
|
3884
|
-
const
|
|
3885
|
-
if (
|
|
3886
|
-
prompt = "
|
|
3101
|
+
const entries = parseEntries(answer);
|
|
3102
|
+
if (entries.length || !required) return entries;
|
|
3103
|
+
prompt = "Please enter at least one entry:";
|
|
3887
3104
|
}
|
|
3888
3105
|
}
|
|
3106
|
+
|
|
3107
|
+
// src/actions/confirmLanguage.ts
|
|
3108
|
+
import z21 from "zod";
|
|
3109
|
+
var confirmLanguageSchema = z21.object({
|
|
3110
|
+
languages: detectLanguageSchema.shape.languages
|
|
3111
|
+
});
|
|
3889
3112
|
async function confirmLanguage(ctx) {
|
|
3890
3113
|
const detected = ctx.getStepOutput("project-scan");
|
|
3891
|
-
const
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
prompt: `Write the ingestion script in ${primary.name}?`,
|
|
3897
|
-
promptType: "acceptReject",
|
|
3898
|
-
options: [`Confirm ${primary.name}`, "Use a different language"],
|
|
3899
|
-
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0],
|
|
3900
|
-
messages: detectedLanguages.length > 1 ? [`Detected: ${summarize(detectedLanguages)}`] : []
|
|
3901
|
-
});
|
|
3902
|
-
if (accepted === true) return confirmed(detectedLanguages);
|
|
3903
|
-
}
|
|
3904
|
-
const options = [...CURATED_LANGUAGES];
|
|
3905
|
-
for (const language of detectedLanguages) {
|
|
3906
|
-
if (!options.some((o) => isSameLanguage(o, language.name))) {
|
|
3907
|
-
options.push(language.name);
|
|
3908
|
-
}
|
|
3909
|
-
}
|
|
3910
|
-
options.push(OTHER_OPTION);
|
|
3911
|
-
const detectedFor = (option) => detectedLanguages.find((l) => isSameLanguage(option, l.name));
|
|
3912
|
-
const secondary = options.map(
|
|
3913
|
-
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
3914
|
-
);
|
|
3915
|
-
const defaultSelectedIndex = Math.max(
|
|
3916
|
-
options.findIndex((o) => detectedFor(o)),
|
|
3917
|
-
0
|
|
3918
|
-
);
|
|
3919
|
-
const selection = await ctx.requestUserInput({
|
|
3920
|
-
prompt: "select the language for your ingestion script",
|
|
3921
|
-
promptType: "multipleChoice",
|
|
3922
|
-
options,
|
|
3923
|
-
secondary,
|
|
3924
|
-
defaultSelectedIndex
|
|
3114
|
+
const answer = await ctx.requestUserInput({
|
|
3115
|
+
prompt: "Did we detect your language(s) correctly?",
|
|
3116
|
+
promptType: "acceptReject",
|
|
3117
|
+
options: ["Yes", "No"],
|
|
3118
|
+
messages: [`Languages: ${summarize(detected.languages)}`]
|
|
3925
3119
|
});
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
}
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3120
|
+
const languages = answer === true ? detected.languages : await askList(ctx, "List the languages your project uses:", {
|
|
3121
|
+
required: true
|
|
3122
|
+
});
|
|
3123
|
+
track("AI Wizard Language Confirmed", {
|
|
3124
|
+
languages
|
|
3125
|
+
});
|
|
3126
|
+
return { languages };
|
|
3932
3127
|
}
|
|
3933
3128
|
|
|
3934
3129
|
// src/actions/confirmFramework.ts
|
|
3935
3130
|
import z22 from "zod";
|
|
3936
|
-
|
|
3937
|
-
// src/lib/frameworks.ts
|
|
3938
|
-
var BACKEND_ONLY_FRAMEWORK = "Backend only / API";
|
|
3939
|
-
var FRAMEWORKS = [
|
|
3940
|
-
// Frontend — InstantSearch component flavors.
|
|
3941
|
-
{ name: "Next.js", strategy: "react", aliases: ["next", "nextjs"] },
|
|
3942
|
-
{ name: "React", strategy: "react", aliases: ["reactjs"] },
|
|
3943
|
-
{ name: "Vue", strategy: "vue", aliases: ["vuejs", "nuxt", "nuxtjs"] },
|
|
3944
|
-
{ name: "Angular", strategy: "angular", aliases: ["angularjs"] },
|
|
3945
|
-
// No Svelte InstantSearch flavor exists, so it uses InstantSearch.js.
|
|
3946
|
-
{ name: "Svelte", strategy: "js", aliases: ["sveltekit"] },
|
|
3947
|
-
{
|
|
3948
|
-
name: "Vanilla JS",
|
|
3949
|
-
strategy: "js",
|
|
3950
|
-
aliases: ["vanilla", "javascript", "js", "astro", "vite"]
|
|
3951
|
-
},
|
|
3952
|
-
// Backend — Algolia's official framework integrations. Server-rendered
|
|
3953
|
-
// templates get InstantSearch.js from a CDN.
|
|
3954
|
-
{
|
|
3955
|
-
name: "Rails",
|
|
3956
|
-
strategy: "cdn-template",
|
|
3957
|
-
aliases: ["rubyonrails", "ruby on rails", "erb"]
|
|
3958
|
-
},
|
|
3959
|
-
{ name: "Django", strategy: "cdn-template", aliases: ["jinja", "jinja2"] },
|
|
3960
|
-
{ name: "Laravel", strategy: "cdn-template", aliases: ["blade"] },
|
|
3961
|
-
{ name: "Symfony", strategy: "cdn-template", aliases: ["twig"] },
|
|
3962
|
-
// Mobile — Algolia ships InstantSearch iOS/Android and Dart clients, but the
|
|
3963
|
-
// wizard can't scaffold a native UI, so it points at the docs instead.
|
|
3964
|
-
{ name: "Flutter", strategy: "none", aliases: [] },
|
|
3965
|
-
{ name: "iOS", strategy: "none", aliases: ["swiftui", "uikit"] },
|
|
3966
|
-
{ name: "Android", strategy: "none", aliases: ["jetpack compose", "compose"] },
|
|
3967
|
-
{ name: BACKEND_ONLY_FRAMEWORK, strategy: "cdn-template", aliases: [] }
|
|
3968
|
-
];
|
|
3969
|
-
var CURATED_FRAMEWORKS = FRAMEWORKS.map(
|
|
3970
|
-
(f) => f.name
|
|
3971
|
-
);
|
|
3972
|
-
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3973
|
-
var ALIAS_TO_NAME = /* @__PURE__ */ new Map();
|
|
3974
|
-
for (const framework of FRAMEWORKS) {
|
|
3975
|
-
for (const alias of [framework.name, ...framework.aliases]) {
|
|
3976
|
-
ALIAS_TO_NAME.set(normalize(alias), framework.name);
|
|
3977
|
-
}
|
|
3978
|
-
}
|
|
3979
|
-
var STRATEGY_BY_NAME = new Map(FRAMEWORKS.map((f) => [f.name, f.strategy]));
|
|
3980
|
-
function canonicalFrameworkName(name) {
|
|
3981
|
-
return ALIAS_TO_NAME.get(normalize(name));
|
|
3982
|
-
}
|
|
3983
|
-
function isSameFramework(a, b) {
|
|
3984
|
-
const x = canonicalFrameworkName(a) ?? normalize(a);
|
|
3985
|
-
const y = canonicalFrameworkName(b) ?? normalize(b);
|
|
3986
|
-
return x !== "" && x === y;
|
|
3987
|
-
}
|
|
3988
|
-
function resolveSearchStrategy(frameworkName, hasJavaScriptInStack) {
|
|
3989
|
-
const canonical = frameworkName ? canonicalFrameworkName(frameworkName) : void 0;
|
|
3990
|
-
const strategy = canonical ? STRATEGY_BY_NAME.get(canonical) : void 0;
|
|
3991
|
-
if (strategy) return strategy;
|
|
3992
|
-
return hasJavaScriptInStack ? "js" : "cdn-template";
|
|
3993
|
-
}
|
|
3994
|
-
function searchDocKey(strategy) {
|
|
3995
|
-
return strategy === "cdn-template" ? "templates" : strategy;
|
|
3996
|
-
}
|
|
3997
|
-
function bundlesJavaScript(strategy) {
|
|
3998
|
-
return strategy !== "cdn-template" && strategy !== "none";
|
|
3999
|
-
}
|
|
4000
|
-
function canScaffoldSearchUI(strategy) {
|
|
4001
|
-
return strategy !== "none";
|
|
4002
|
-
}
|
|
4003
|
-
var ENV_PREFIXES = [
|
|
4004
|
-
{ aliases: ["next", "nextjs"], prefix: "NEXT_PUBLIC_" },
|
|
4005
|
-
{ aliases: ["nuxt", "nuxtjs"], prefix: "NUXT_PUBLIC_" },
|
|
4006
|
-
{ aliases: ["astro"], prefix: "PUBLIC_" },
|
|
4007
|
-
{ aliases: ["vite"], prefix: "VITE_" }
|
|
4008
|
-
];
|
|
4009
|
-
var DEFAULT_ENV_PREFIX = "PUBLIC_";
|
|
4010
|
-
function publicEnvPrefix(frameworkNames, strategy) {
|
|
4011
|
-
if (!bundlesJavaScript(strategy)) return "";
|
|
4012
|
-
const present = new Set(frameworkNames.map(normalize));
|
|
4013
|
-
for (const { aliases, prefix } of ENV_PREFIXES) {
|
|
4014
|
-
if (aliases.some((alias) => present.has(alias))) return prefix;
|
|
4015
|
-
}
|
|
4016
|
-
return DEFAULT_ENV_PREFIX;
|
|
4017
|
-
}
|
|
4018
|
-
function describeSearchTarget(strategy, frameworkName) {
|
|
4019
|
-
switch (strategy) {
|
|
4020
|
-
case "react":
|
|
4021
|
-
return "React (react-instantsearch)";
|
|
4022
|
-
case "vue":
|
|
4023
|
-
return "Vue (vue-instantsearch)";
|
|
4024
|
-
case "angular":
|
|
4025
|
-
return "Angular (angular-instantsearch)";
|
|
4026
|
-
case "js":
|
|
4027
|
-
return "plain JavaScript (InstantSearch.js)";
|
|
4028
|
-
case "cdn-template":
|
|
4029
|
-
return `${frameworkName ?? "server-rendered"} templates (InstantSearch.js via CDN)`;
|
|
4030
|
-
case "none":
|
|
4031
|
-
return frameworkName ?? "a native mobile app";
|
|
4032
|
-
}
|
|
4033
|
-
}
|
|
4034
|
-
|
|
4035
|
-
// src/actions/confirmFramework.ts
|
|
4036
3131
|
var confirmFrameworkSchema = z22.object({
|
|
4037
3132
|
frameworks: detectLanguageSchema.shape.frameworks
|
|
4038
3133
|
});
|
|
4039
|
-
var
|
|
4040
|
-
|
|
3134
|
+
var CURATED_FRAMEWORKS = [
|
|
3135
|
+
"Next.js",
|
|
3136
|
+
"React",
|
|
3137
|
+
"Vue",
|
|
3138
|
+
"Angular",
|
|
3139
|
+
"Svelte",
|
|
3140
|
+
"Vanilla JS"
|
|
3141
|
+
];
|
|
3142
|
+
var OTHER_OPTION = "Other";
|
|
3143
|
+
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3144
|
+
var FRAMEWORK_ALIASES = {
|
|
3145
|
+
next: "nextjs",
|
|
3146
|
+
nextjs: "nextjs",
|
|
3147
|
+
react: "react",
|
|
3148
|
+
reactjs: "react",
|
|
3149
|
+
vue: "vue",
|
|
3150
|
+
vuejs: "vue",
|
|
3151
|
+
angular: "angular",
|
|
3152
|
+
angularjs: "angular",
|
|
3153
|
+
svelte: "svelte",
|
|
3154
|
+
sveltekit: "svelte",
|
|
3155
|
+
vanillajs: "vanillajs",
|
|
3156
|
+
vanilla: "vanillajs",
|
|
3157
|
+
javascript: "vanillajs",
|
|
3158
|
+
js: "vanillajs"
|
|
3159
|
+
};
|
|
3160
|
+
var isSameFramework = (a, b) => {
|
|
3161
|
+
const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
|
|
3162
|
+
const y = FRAMEWORK_ALIASES[normalize(b)] ?? normalize(b);
|
|
3163
|
+
return x !== "" && x === y;
|
|
3164
|
+
};
|
|
3165
|
+
function confirmed(name, version) {
|
|
4041
3166
|
const frameworks = [{ name, version: version ?? "unknown" }];
|
|
4042
3167
|
track("AI Wizard Frontend Framework Confirmed", { frameworks });
|
|
4043
3168
|
return { frameworks };
|
|
@@ -4065,7 +3190,7 @@ async function confirmFramework(ctx) {
|
|
|
4065
3190
|
for (const fw of detectedFrameworks) {
|
|
4066
3191
|
if (!options.some((o) => isSameFramework(o, fw.name))) options.push(fw.name);
|
|
4067
3192
|
}
|
|
4068
|
-
options.push(
|
|
3193
|
+
options.push(OTHER_OPTION);
|
|
4069
3194
|
const detectedFor = (option) => detectedFrameworks.find((fw) => isSameFramework(option, fw.name));
|
|
4070
3195
|
const primary = detectedFrameworks[0];
|
|
4071
3196
|
if (primary) {
|
|
@@ -4075,7 +3200,7 @@ async function confirmFramework(ctx) {
|
|
|
4075
3200
|
options: [`Confirm ${primary.name}`, "Use a different framework"],
|
|
4076
3201
|
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0]
|
|
4077
3202
|
});
|
|
4078
|
-
if (accepted === true) return
|
|
3203
|
+
if (accepted === true) return confirmed(primary.name, primary.version);
|
|
4079
3204
|
}
|
|
4080
3205
|
const secondary = options.map(
|
|
4081
3206
|
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
@@ -4085,7 +3210,7 @@ async function confirmFramework(ctx) {
|
|
|
4085
3210
|
0
|
|
4086
3211
|
);
|
|
4087
3212
|
const selection = await ctx.requestUserInput({
|
|
4088
|
-
prompt: "select
|
|
3213
|
+
prompt: "select a framework",
|
|
4089
3214
|
promptType: "multipleChoice",
|
|
4090
3215
|
options,
|
|
4091
3216
|
secondary,
|
|
@@ -4094,10 +3219,10 @@ async function confirmFramework(ctx) {
|
|
|
4094
3219
|
if (typeof selection !== "string") {
|
|
4095
3220
|
throw new Error("confirmFramework received an unexpected non-text result");
|
|
4096
3221
|
}
|
|
4097
|
-
if (selection ===
|
|
4098
|
-
return
|
|
3222
|
+
if (selection === OTHER_OPTION) {
|
|
3223
|
+
return confirmed(await askOtherFramework(ctx));
|
|
4099
3224
|
}
|
|
4100
|
-
return
|
|
3225
|
+
return confirmed(selection, detectedFor(selection)?.version);
|
|
4101
3226
|
}
|
|
4102
3227
|
|
|
4103
3228
|
// src/actions/promptUser.ts
|
|
@@ -4190,15 +3315,15 @@ async function confirmEntities(ctx) {
|
|
|
4190
3315
|
onSubmit: () => {
|
|
4191
3316
|
}
|
|
4192
3317
|
});
|
|
4193
|
-
const
|
|
4194
|
-
if (
|
|
3318
|
+
const confirmed2 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
|
|
3319
|
+
if (confirmed2.length === 0) {
|
|
4195
3320
|
throw new Error("User cancelled entity selection \u2014 analysis halted.");
|
|
4196
3321
|
}
|
|
4197
|
-
ctx.setUserInput("confirmedEntities",
|
|
3322
|
+
ctx.setUserInput("confirmedEntities", confirmed2);
|
|
4198
3323
|
track("AI Wizard Entities Confirmed", {
|
|
4199
|
-
entities: toEntitySummary(
|
|
3324
|
+
entities: toEntitySummary(confirmed2)
|
|
4200
3325
|
});
|
|
4201
|
-
return { ingestionAnalysis: entities, confirmedEntities:
|
|
3326
|
+
return { ingestionAnalysis: entities, confirmedEntities: confirmed2 };
|
|
4202
3327
|
}
|
|
4203
3328
|
|
|
4204
3329
|
// src/actions/review.ts
|
|
@@ -4222,7 +3347,7 @@ ${JSON.stringify(s.output, null, 2)}`
|
|
|
4222
3347
|
}
|
|
4223
3348
|
function formatReviewSummary(result) {
|
|
4224
3349
|
const nextStepLines = result.nextSteps.map((step) => {
|
|
4225
|
-
const isIngestCommand = step.includes("algolia-wizard/
|
|
3350
|
+
const isIngestCommand = step.includes(".algolia-wizard/ingest.sh");
|
|
4226
3351
|
const isWorktreeCommand = step.includes("/worktrees/");
|
|
4227
3352
|
return {
|
|
4228
3353
|
text: `\u2192 ${step}`,
|
|
@@ -4264,14 +3389,13 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
4264
3389
|
import z25 from "zod";
|
|
4265
3390
|
|
|
4266
3391
|
// src/lib/worktree.ts
|
|
4267
|
-
import { execFile } from "node:child_process";
|
|
4268
|
-
import {
|
|
4269
|
-
import { copyFile, mkdir as mkdir6, readdir as readdir4, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3392
|
+
import { execFile, spawn as spawn3 } from "node:child_process";
|
|
3393
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
4270
3394
|
import {
|
|
4271
3395
|
basename as basename2,
|
|
4272
3396
|
dirname as dirname7,
|
|
4273
3397
|
isAbsolute as isAbsolute2,
|
|
4274
|
-
join as
|
|
3398
|
+
join as join9,
|
|
4275
3399
|
relative as relative2,
|
|
4276
3400
|
resolve as resolve3
|
|
4277
3401
|
} from "node:path";
|
|
@@ -4305,8 +3429,8 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
4305
3429
|
return out.trim().length > 0;
|
|
4306
3430
|
}
|
|
4307
3431
|
async function pruneOldWorktrees(repoRoot) {
|
|
4308
|
-
const dir =
|
|
4309
|
-
const stale = (await
|
|
3432
|
+
const dir = join9(stateDir(repoRoot), "worktrees");
|
|
3433
|
+
const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
4310
3434
|
for (const slug of stale) {
|
|
4311
3435
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
4312
3436
|
try {
|
|
@@ -4316,7 +3440,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
4316
3440
|
"worktree",
|
|
4317
3441
|
"remove",
|
|
4318
3442
|
"--force",
|
|
4319
|
-
|
|
3443
|
+
join9(dir, slug)
|
|
4320
3444
|
]);
|
|
4321
3445
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
4322
3446
|
} catch (err) {
|
|
@@ -4330,55 +3454,43 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
4330
3454
|
async function createWorktree(repoRoot) {
|
|
4331
3455
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
4332
3456
|
const dirSlug = branch.replace(/\//g, "-");
|
|
4333
|
-
const path =
|
|
3457
|
+
const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
|
|
4334
3458
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
4335
3459
|
await pruneOldWorktrees(repoRoot);
|
|
4336
3460
|
await mkdir6(dirname7(path), { recursive: true });
|
|
4337
3461
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
4338
3462
|
return { path, branch };
|
|
4339
3463
|
}
|
|
4340
|
-
async function
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
return { ok: code === 0, output: output.trim() };
|
|
4346
|
-
}
|
|
4347
|
-
async function installWorktreeDeps(worktreePath, toolchain) {
|
|
4348
|
-
const { profile, installSteps, packageManager } = toolchain;
|
|
4349
|
-
const declared = packageManager.dependency.mode === "agent-declares" ? packageManager.dependency.file : void 0;
|
|
4350
|
-
const haveSomethingToInstall = await hasProfileManifest(worktreePath, profile) || declared !== void 0 && existsSync4(join11(worktreePath, declared));
|
|
4351
|
-
if (!haveSomethingToInstall) {
|
|
4352
|
-
return {
|
|
4353
|
-
ok: true,
|
|
4354
|
-
output: `no ${profile.displayName} manifest; skipped install`
|
|
4355
|
-
};
|
|
4356
|
-
}
|
|
4357
|
-
if (installSteps.length === 0) {
|
|
4358
|
-
return {
|
|
4359
|
-
ok: true,
|
|
4360
|
-
output: `${profile.displayName} (${toolchain.packageManager.id}) has no wizard-run install step`
|
|
4361
|
-
};
|
|
4362
|
-
}
|
|
4363
|
-
const outputs = [];
|
|
4364
|
-
for (const step of installSteps) {
|
|
4365
|
-
if (step.requiresFile && !existsSync4(join11(worktreePath, step.requiresFile)))
|
|
4366
|
-
continue;
|
|
4367
|
-
const result = await spawnStep(worktreePath, step.argv);
|
|
4368
|
-
if (result.output) outputs.push(result.output);
|
|
4369
|
-
if (result.ok) continue;
|
|
4370
|
-
if (step.optional) {
|
|
4371
|
-
logger.warn(
|
|
4372
|
-
{ step: step.argv.join(" "), output: result.output },
|
|
4373
|
-
"installWorktreeDeps: optional install step failed; continuing"
|
|
4374
|
-
);
|
|
4375
|
-
continue;
|
|
4376
|
-
}
|
|
4377
|
-
return { ok: false, output: outputs.join("\n").trim() };
|
|
3464
|
+
async function installWorktreeDeps(worktreePath) {
|
|
3465
|
+
try {
|
|
3466
|
+
await readPackageJson(worktreePath);
|
|
3467
|
+
} catch {
|
|
3468
|
+
return { ok: true, output: "no package.json; skipped install" };
|
|
4378
3469
|
}
|
|
4379
|
-
|
|
3470
|
+
const pm = await detectPackageManager(worktreePath);
|
|
3471
|
+
return new Promise((resolve4) => {
|
|
3472
|
+
let output = "";
|
|
3473
|
+
const child = spawn3(pm, ["install"], {
|
|
3474
|
+
cwd: worktreePath,
|
|
3475
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3476
|
+
});
|
|
3477
|
+
child.stdout?.on("data", (d) => output += d);
|
|
3478
|
+
child.stderr?.on("data", (d) => output += d);
|
|
3479
|
+
child.on(
|
|
3480
|
+
"error",
|
|
3481
|
+
(err) => resolve4({
|
|
3482
|
+
ok: false,
|
|
3483
|
+
output: `Failed to run ${pm} install: ${err.message}`
|
|
3484
|
+
})
|
|
3485
|
+
);
|
|
3486
|
+
child.on(
|
|
3487
|
+
"close",
|
|
3488
|
+
(code) => resolve4({ ok: code === 0, output: output.trim() })
|
|
3489
|
+
);
|
|
3490
|
+
});
|
|
4380
3491
|
}
|
|
4381
|
-
|
|
3492
|
+
var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
|
|
3493
|
+
function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
4382
3494
|
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
4383
3495
|
return {
|
|
4384
3496
|
ok: false,
|
|
@@ -4393,29 +3505,18 @@ function validateIngestEntrypoint(worktreePath, entrypoint, allowedExtensions) {
|
|
|
4393
3505
|
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
4394
3506
|
};
|
|
4395
3507
|
}
|
|
4396
|
-
if (allowedExtensions?.length && !allowedExtensions.some((ext) => entrypoint.endsWith(ext))) {
|
|
4397
|
-
return {
|
|
4398
|
-
ok: false,
|
|
4399
|
-
reason: `entrypoint "${entrypoint}" is not one of ${allowedExtensions.join(", ")}`
|
|
4400
|
-
};
|
|
4401
|
-
}
|
|
4402
3508
|
return { ok: true, target };
|
|
4403
3509
|
}
|
|
4404
|
-
async function runIngestScript(worktreePath,
|
|
4405
|
-
|
|
4406
|
-
if (ingest.kind !== "auto") {
|
|
3510
|
+
async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
3511
|
+
if (!INGEST_RUNTIMES.includes(runtime)) {
|
|
4407
3512
|
return {
|
|
4408
3513
|
ran: false,
|
|
4409
3514
|
ok: false,
|
|
4410
3515
|
output: "",
|
|
4411
|
-
reason:
|
|
3516
|
+
reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
|
|
4412
3517
|
};
|
|
4413
3518
|
}
|
|
4414
|
-
const validated = validateIngestEntrypoint(
|
|
4415
|
-
worktreePath,
|
|
4416
|
-
entrypoint,
|
|
4417
|
-
ingest.entrypointExtensions
|
|
4418
|
-
);
|
|
3519
|
+
const validated = validateIngestEntrypoint(worktreePath, entrypoint);
|
|
4419
3520
|
if (!validated.ok) {
|
|
4420
3521
|
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
4421
3522
|
}
|
|
@@ -4436,13 +3537,29 @@ async function runIngestScript(worktreePath, toolchain, entrypoint, env = {}) {
|
|
|
4436
3537
|
reason: `entrypoint "${entrypoint}" does not exist`
|
|
4437
3538
|
};
|
|
4438
3539
|
}
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
3540
|
+
return new Promise((resolveRun) => {
|
|
3541
|
+
let output = "";
|
|
3542
|
+
const child = spawn3(runtime, [entrypoint], {
|
|
3543
|
+
cwd: worktreePath,
|
|
3544
|
+
shell: false,
|
|
3545
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3546
|
+
env: { ...process.env, ...env }
|
|
3547
|
+
});
|
|
3548
|
+
child.stdout?.on("data", (d) => output += d);
|
|
3549
|
+
child.stderr?.on("data", (d) => output += d);
|
|
3550
|
+
child.on(
|
|
3551
|
+
"error",
|
|
3552
|
+
(err) => resolveRun({
|
|
3553
|
+
ran: true,
|
|
3554
|
+
ok: false,
|
|
3555
|
+
output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
|
|
3556
|
+
})
|
|
3557
|
+
);
|
|
3558
|
+
child.on(
|
|
3559
|
+
"close",
|
|
3560
|
+
(code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
|
|
3561
|
+
);
|
|
4444
3562
|
});
|
|
4445
|
-
return { ran: true, ok: code === 0, output: output.trim() };
|
|
4446
3563
|
}
|
|
4447
3564
|
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
4448
3565
|
const trimmed = sourcePath.trim();
|
|
@@ -4457,8 +3574,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
4457
3574
|
} catch {
|
|
4458
3575
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
4459
3576
|
}
|
|
4460
|
-
const relPath =
|
|
4461
|
-
const dest =
|
|
3577
|
+
const relPath = join9(ingestDir, basename2(source));
|
|
3578
|
+
const dest = join9(worktreePath, relPath);
|
|
4462
3579
|
try {
|
|
4463
3580
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
4464
3581
|
await copyFile(source, dest);
|
|
@@ -4474,7 +3591,7 @@ function hasEnvVar(content, name) {
|
|
|
4474
3591
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
4475
3592
|
}
|
|
4476
3593
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
4477
|
-
const target =
|
|
3594
|
+
const target = join9(worktreePath, ".env");
|
|
4478
3595
|
let existing = "";
|
|
4479
3596
|
try {
|
|
4480
3597
|
existing = await readFile7(target, "utf8");
|
|
@@ -4546,33 +3663,69 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
|
|
|
4546
3663
|
}
|
|
4547
3664
|
|
|
4548
3665
|
// src/lib/algoliaDocs.ts
|
|
4549
|
-
import { readFileSync, existsSync as
|
|
4550
|
-
import { dirname as dirname8, join as
|
|
3666
|
+
import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
|
|
3667
|
+
import { dirname as dirname8, join as join10 } from "node:path";
|
|
4551
3668
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4552
|
-
var DOCS_SUBPATH =
|
|
3669
|
+
var DOCS_SUBPATH = join10("docs", "algolia-sdk");
|
|
4553
3670
|
function findDocsDir() {
|
|
4554
3671
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
4555
3672
|
for (; ; ) {
|
|
4556
|
-
const candidate =
|
|
4557
|
-
if (
|
|
3673
|
+
const candidate = join10(dir, DOCS_SUBPATH);
|
|
3674
|
+
if (existsSync2(candidate)) return candidate;
|
|
4558
3675
|
const parent = dirname8(dir);
|
|
4559
3676
|
if (parent === dir) return void 0;
|
|
4560
3677
|
dir = parent;
|
|
4561
3678
|
}
|
|
4562
3679
|
}
|
|
4563
|
-
function
|
|
3680
|
+
function loadAlgoliaDoc(language) {
|
|
3681
|
+
const docsDir = findDocsDir();
|
|
3682
|
+
if (!docsDir) {
|
|
3683
|
+
logger.warn(
|
|
3684
|
+
"algoliaDocs: docs/algolia-sdk not found; skipping SDK reference"
|
|
3685
|
+
);
|
|
3686
|
+
return "";
|
|
3687
|
+
}
|
|
3688
|
+
const files = readdirSync(docsDir).filter((f) => f.includes(language));
|
|
3689
|
+
if (files.length === 0) {
|
|
3690
|
+
logger.warn(
|
|
3691
|
+
{ language },
|
|
3692
|
+
"algoliaDocs: no SDK reference found for language; skipping"
|
|
3693
|
+
);
|
|
3694
|
+
return "";
|
|
3695
|
+
}
|
|
3696
|
+
return readFileSync(join10(docsDir, files[0]), "utf8").trim();
|
|
3697
|
+
}
|
|
3698
|
+
function getNamedDoc(name, language) {
|
|
4564
3699
|
const docsDir = findDocsDir();
|
|
4565
3700
|
if (!docsDir) {
|
|
4566
3701
|
logger.warn("docs/algolia-sdk not found");
|
|
4567
3702
|
return "";
|
|
4568
3703
|
}
|
|
4569
|
-
const file =
|
|
4570
|
-
if (!
|
|
4571
|
-
logger.warn({ name,
|
|
3704
|
+
const file = join10(docsDir, `${name}-${language}.md`);
|
|
3705
|
+
if (!existsSync2(file)) {
|
|
3706
|
+
logger.warn({ name, language }, "named SDK reference not found");
|
|
4572
3707
|
return "";
|
|
4573
3708
|
}
|
|
4574
3709
|
return readFileSync(file, "utf8").trim();
|
|
4575
3710
|
}
|
|
3711
|
+
function getFrameworkSpecificDoc(frameworks) {
|
|
3712
|
+
const fw = frameworks.map((f) => f.toLowerCase());
|
|
3713
|
+
if (fw.includes("vue") || fw.includes("nuxt")) {
|
|
3714
|
+
return loadAlgoliaDoc("vue");
|
|
3715
|
+
}
|
|
3716
|
+
if (fw.includes("react") || fw.includes("next.js")) {
|
|
3717
|
+
return loadAlgoliaDoc("react");
|
|
3718
|
+
}
|
|
3719
|
+
if (fw.includes("angular")) {
|
|
3720
|
+
return loadAlgoliaDoc("angular");
|
|
3721
|
+
}
|
|
3722
|
+
return loadAlgoliaDoc("js");
|
|
3723
|
+
}
|
|
3724
|
+
|
|
3725
|
+
// src/lib/shell.ts
|
|
3726
|
+
function shellQuote(value) {
|
|
3727
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
3728
|
+
}
|
|
4576
3729
|
|
|
4577
3730
|
// src/actions/implement.ts
|
|
4578
3731
|
var implementSchema = z25.object({
|
|
@@ -4607,11 +3760,12 @@ var implementSchema = z25.object({
|
|
|
4607
3760
|
});
|
|
4608
3761
|
var implementationOutputSchema = z25.object({
|
|
4609
3762
|
summary: z25.string(),
|
|
4610
|
-
// Ingestion only:
|
|
4611
|
-
//
|
|
4612
|
-
//
|
|
4613
|
-
//
|
|
4614
|
-
//
|
|
3763
|
+
// Ingestion only: how to run the generated script, as a structured pair the
|
|
3764
|
+
// wizard turns into an argv (`<runtime> <entrypoint>`) — never a free-form
|
|
3765
|
+
// command string. `runtime` is constrained to an allowlisted interpreter and
|
|
3766
|
+
// `entrypoint` is validated to a worktree-relative path before execution, so
|
|
3767
|
+
// the agent cannot inject extra commands or swap the interpreter.
|
|
3768
|
+
runtime: z25.enum(INGEST_RUNTIMES).optional(),
|
|
4615
3769
|
entrypoint: z25.string().optional()
|
|
4616
3770
|
});
|
|
4617
3771
|
var verificationOutputSchema = z25.object({
|
|
@@ -4621,11 +3775,47 @@ var verificationOutputSchema = z25.object({
|
|
|
4621
3775
|
});
|
|
4622
3776
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
4623
3777
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
3778
|
+
var INGEST_DIR = ".algolia-wizard";
|
|
3779
|
+
function detectUiFramework(language) {
|
|
3780
|
+
const names = language.frameworks.map((f) => f.name.toLowerCase());
|
|
3781
|
+
if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
|
|
3782
|
+
if (names.some((n) => n.includes("react") || n.includes("next")))
|
|
3783
|
+
return "React";
|
|
3784
|
+
if (names.some((n) => n.includes("angular"))) return "Angular";
|
|
3785
|
+
return "JavaScript";
|
|
3786
|
+
}
|
|
3787
|
+
function frameworksForDoc(framework) {
|
|
3788
|
+
switch (framework) {
|
|
3789
|
+
case "React":
|
|
3790
|
+
return ["react"];
|
|
3791
|
+
case "Vue":
|
|
3792
|
+
return ["vue"];
|
|
3793
|
+
case "Angular":
|
|
3794
|
+
return ["angular"];
|
|
3795
|
+
case "JavaScript":
|
|
3796
|
+
return [];
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
function publicEnvPrefix(language) {
|
|
3800
|
+
const frameworkNames = language.frameworks.map(
|
|
3801
|
+
(framework) => framework.name.toLowerCase()
|
|
4628
3802
|
);
|
|
3803
|
+
if (frameworkNames.some((name) => name.includes("next"))) {
|
|
3804
|
+
return "NEXT_PUBLIC_";
|
|
3805
|
+
}
|
|
3806
|
+
if (frameworkNames.some((name) => name.includes("nuxt"))) {
|
|
3807
|
+
return "NUXT_PUBLIC_";
|
|
3808
|
+
}
|
|
3809
|
+
if (frameworkNames.some((name) => name.includes("astro"))) {
|
|
3810
|
+
return "PUBLIC_";
|
|
3811
|
+
}
|
|
3812
|
+
if (frameworkNames.some((name) => name.includes("vite"))) {
|
|
3813
|
+
return "VITE_";
|
|
3814
|
+
}
|
|
3815
|
+
return "PUBLIC_";
|
|
3816
|
+
}
|
|
3817
|
+
function searchEnvVars(language, appId, searchKey) {
|
|
3818
|
+
const prefix = publicEnvPrefix(language);
|
|
4629
3819
|
return [
|
|
4630
3820
|
{
|
|
4631
3821
|
name: `${prefix}ALGOLIA_APP_ID`,
|
|
@@ -4637,38 +3827,6 @@ function buildSearchEnvVars(language, strategy, appId, searchKey) {
|
|
|
4637
3827
|
}
|
|
4638
3828
|
];
|
|
4639
3829
|
}
|
|
4640
|
-
async function resolveIngestionProfile(ctx, language, repoRoot) {
|
|
4641
|
-
const { candidates, confirmed: confirmed3, onDisk } = await pickIngestionCandidates(
|
|
4642
|
-
repoRoot,
|
|
4643
|
-
language.languages.map((l) => l.name)
|
|
4644
|
-
);
|
|
4645
|
-
if (candidates.length === 0) {
|
|
4646
|
-
const chosen = confirmed3[0] ?? onDisk[0] ?? LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID];
|
|
4647
|
-
logger.warn(
|
|
4648
|
-
{
|
|
4649
|
-
confirmed: language.languages.map((l) => l.name),
|
|
4650
|
-
onDisk: onDisk.map((p) => p.id),
|
|
4651
|
-
chosen: chosen.id
|
|
4652
|
-
},
|
|
4653
|
-
"implement: no confirmed language matched a manifest on disk; falling back"
|
|
4654
|
-
);
|
|
4655
|
-
return chosen;
|
|
4656
|
-
}
|
|
4657
|
-
if (candidates.length === 1) return candidates[0];
|
|
4658
|
-
const backends = candidates.filter(isBackendLanguage);
|
|
4659
|
-
if (backends.length === 1) return backends[0];
|
|
4660
|
-
if (backends.length === 0) return candidates[0];
|
|
4661
|
-
if (isBackendLanguage(candidates[0])) return candidates[0];
|
|
4662
|
-
const options = backends.map((p) => p.displayName);
|
|
4663
|
-
const selection = await ctx.requestUserInput({
|
|
4664
|
-
prompt: "Which language should the ingestion script use?",
|
|
4665
|
-
promptType: "multipleChoice",
|
|
4666
|
-
options,
|
|
4667
|
-
defaultSelectedIndex: 0
|
|
4668
|
-
});
|
|
4669
|
-
const picked = typeof selection === "string" ? backends.find((p) => p.displayName === selection) : void 0;
|
|
4670
|
-
return picked ?? backends[0];
|
|
4671
|
-
}
|
|
4672
3830
|
function baseInstructions(input) {
|
|
4673
3831
|
return [
|
|
4674
3832
|
`Target Algolia index: ${input.targetIndex}`,
|
|
@@ -4696,48 +3854,37 @@ function sourceSpecificInstructions(input) {
|
|
|
4696
3854
|
generated: [
|
|
4697
3855
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
4698
3856
|
"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.",
|
|
4699
|
-
"In the script, read and parse each returned
|
|
3857
|
+
"In the script, read and parse each returned file path at runtime (e.g. JSON.parse(readFileSync(...)) in Node/Bun, json.load(open(...)) in Python) instead of inlining the records as literals.",
|
|
4700
3858
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
4701
3859
|
]
|
|
4702
3860
|
};
|
|
4703
3861
|
return byLine[input.ingestionSource];
|
|
4704
3862
|
}
|
|
4705
3863
|
function ingestionInstructions(input) {
|
|
4706
|
-
const { ingestionProfile: profile, toolchain } = input;
|
|
4707
|
-
const { ingest } = toolchain;
|
|
4708
|
-
const extensions = ingest.entrypointExtensions.join(", ");
|
|
4709
|
-
const runInstruction = ingest.kind === "auto" ? `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard runs it with \`${describeIngestCommand(ingest, profile.ingestEntrypointExample)}\`, so it must be a plain path with no flags or arguments and must run as-is under that command.` : `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard does NOT run ${profile.displayName} ${toolchain.packageManager.id} projects itself \u2014 it tells the developer to run \`${describeIngestCommand(ingest, profile.ingestEntrypointExample)}\`, so also add whatever build configuration that command needs.`;
|
|
4710
3864
|
return [
|
|
4711
3865
|
...input.confirmed && input.confirmed.length ? [
|
|
4712
|
-
`
|
|
3866
|
+
`Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
|
|
4713
3867
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
4714
|
-
`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.
|
|
4715
|
-
|
|
3868
|
+
`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.`,
|
|
3869
|
+
"Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
|
|
4716
3870
|
"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.",
|
|
4717
|
-
getNamedDoc("save-records",
|
|
4718
|
-
|
|
3871
|
+
getNamedDoc("save-records", "js"),
|
|
3872
|
+
'Add algoliasearch to package.json "dependencies" with a valid version range; the wizard installs the worktree deps after you finish.',
|
|
4719
3873
|
"The summary should be extremely concise.",
|
|
4720
|
-
|
|
3874
|
+
`Return how to run the script as two fields, not a command string: "runtime" (one of ${INGEST_RUNTIMES.join(", ")}) and "entrypoint" (the script path relative to the worktree root, e.g. "${input.ingestDir}/ingest.mjs"). The wizard runs \`<runtime> <entrypoint>\` directly, so the entrypoint must be a plain path with no flags or arguments. Write a script one of those interpreters can run as-is.`,
|
|
4721
3875
|
...sourceSpecificInstructions(input)
|
|
4722
3876
|
] : []
|
|
4723
3877
|
];
|
|
4724
3878
|
}
|
|
4725
3879
|
function searchInstructions(input) {
|
|
4726
|
-
const doc =
|
|
4727
|
-
"instantsearch-setup",
|
|
4728
|
-
searchDocKey(input.searchStrategy)
|
|
4729
|
-
);
|
|
4730
|
-
const isTemplate = input.searchStrategy === "cdn-template";
|
|
4731
|
-
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.`;
|
|
3880
|
+
const doc = getFrameworkSpecificDoc(frameworksForDoc(input.uiFramework));
|
|
4732
3881
|
return [
|
|
4733
3882
|
"Implement an in-app Algolia search experience.",
|
|
4734
|
-
`Build the search UI for ${
|
|
4735
|
-
"Follow the Algolia reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3883
|
+
`Build the search UI for ${input.uiFramework}.`,
|
|
3884
|
+
"Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
4736
3885
|
doc,
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
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.',
|
|
4740
|
-
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.",
|
|
3886
|
+
`Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app \u2014 at least a working SearchBox and Hits against the "${input.targetIndex}" index.`,
|
|
3887
|
+
"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.",
|
|
4741
3888
|
// appId always resolves (requireApplication throws otherwise); only the
|
|
4742
3889
|
// search-only key is best-effort and can fall back to a placeholder.
|
|
4743
3890
|
`Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
@@ -4745,22 +3892,20 @@ function searchInstructions(input) {
|
|
|
4745
3892
|
// resolved app id / search-only key into ".env" under these exact names
|
|
4746
3893
|
// right after this step, so a renamed prefix here would leave the code
|
|
4747
3894
|
// reading a var the wizard never wrote.
|
|
4748
|
-
`Use exactly these env var names: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3895
|
+
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3896
|
+
'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.',
|
|
4749
3897
|
"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."
|
|
4750
3898
|
];
|
|
4751
3899
|
}
|
|
4752
3900
|
function verificationInstructions(input) {
|
|
4753
|
-
const protectedDirs = [
|
|
4754
|
-
.../* @__PURE__ */ new Set([input.ingestDir, ingestScriptDir(input.ingestionProfile)])
|
|
4755
|
-
];
|
|
4756
3901
|
return [
|
|
4757
3902
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
4758
3903
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
4759
|
-
|
|
3904
|
+
"Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
|
|
4760
3905
|
"For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
|
|
4761
3906
|
"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.",
|
|
4762
3907
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
4763
|
-
`Do not modify ${
|
|
3908
|
+
`Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
|
|
4764
3909
|
"Always call reportStatus with status=success once verification has run, even when sufficient=false.",
|
|
4765
3910
|
"Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
|
|
4766
3911
|
"Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
|
|
@@ -4769,17 +3914,14 @@ function verificationInstructions(input) {
|
|
|
4769
3914
|
var IMPLEMENT_CONFIG = {
|
|
4770
3915
|
ingestion: {
|
|
4771
3916
|
title: "Algolia ingestion",
|
|
4772
|
-
label: "Ingestion",
|
|
4773
3917
|
buildInstructions: ingestionInstructions
|
|
4774
3918
|
},
|
|
4775
3919
|
search: {
|
|
4776
3920
|
title: "Algolia search",
|
|
4777
|
-
label: "Search",
|
|
4778
3921
|
buildInstructions: searchInstructions
|
|
4779
3922
|
},
|
|
4780
3923
|
verification: {
|
|
4781
3924
|
title: "Algolia verification",
|
|
4782
|
-
label: "Verification",
|
|
4783
3925
|
buildInstructions: verificationInstructions
|
|
4784
3926
|
}
|
|
4785
3927
|
};
|
|
@@ -4811,10 +3953,11 @@ function buildAgentInstructions(useCase, input, extraInstructions = []) {
|
|
|
4811
3953
|
];
|
|
4812
3954
|
}
|
|
4813
3955
|
function formatSummary(useCase, summary) {
|
|
4814
|
-
|
|
3956
|
+
const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
|
|
3957
|
+
return `${label}: ${summary}`;
|
|
4815
3958
|
}
|
|
4816
|
-
function buildIngestCommand(worktree,
|
|
4817
|
-
return `cd ${shellQuote(worktree)} && ${
|
|
3959
|
+
function buildIngestCommand(worktree, runtime, entrypoint) {
|
|
3960
|
+
return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
|
|
4818
3961
|
}
|
|
4819
3962
|
function parseIngestRecordCount(output) {
|
|
4820
3963
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
@@ -4897,7 +4040,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4897
4040
|
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
4898
4041
|
}
|
|
4899
4042
|
const normalized = normalizeFindingPaths(findings);
|
|
4900
|
-
const
|
|
4043
|
+
const confirmed2 = normalized.confirmedEntities;
|
|
4901
4044
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
4902
4045
|
let appId;
|
|
4903
4046
|
let searchKey;
|
|
@@ -4935,66 +4078,31 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4935
4078
|
);
|
|
4936
4079
|
}
|
|
4937
4080
|
}
|
|
4938
|
-
const ingestionProfile = await resolveIngestionProfile(
|
|
4939
|
-
ctx,
|
|
4940
|
-
language,
|
|
4941
|
-
worktree
|
|
4942
|
-
);
|
|
4943
|
-
const toolchain = await resolveToolchain(worktree, ingestionProfile);
|
|
4944
|
-
const verificationLanguages = [
|
|
4945
|
-
.../* @__PURE__ */ new Set([
|
|
4946
|
-
ingestionProfile.id,
|
|
4947
|
-
...(await detectProfilesFromManifests(worktree)).map((p) => p.id)
|
|
4948
|
-
])
|
|
4949
|
-
];
|
|
4950
|
-
const frameworkName = language.frameworks[0]?.name;
|
|
4951
|
-
const searchStrategy = resolveSearchStrategy(
|
|
4952
|
-
frameworkName,
|
|
4953
|
-
verificationLanguages.includes(JAVASCRIPT)
|
|
4954
|
-
);
|
|
4955
|
-
logger.info(
|
|
4956
|
-
{
|
|
4957
|
-
language: ingestionProfile.id,
|
|
4958
|
-
packageManager: toolchain.packageManager.id,
|
|
4959
|
-
ingest: toolchain.ingest.kind,
|
|
4960
|
-
framework: frameworkName,
|
|
4961
|
-
searchStrategy
|
|
4962
|
-
},
|
|
4963
|
-
"implement: resolved ingestion toolchain and search strategy"
|
|
4964
|
-
);
|
|
4965
4081
|
const input = {
|
|
4966
4082
|
findings: normalized,
|
|
4967
|
-
confirmed:
|
|
4083
|
+
confirmed: confirmed2,
|
|
4968
4084
|
searchLocation,
|
|
4969
4085
|
targetIndex,
|
|
4970
4086
|
language,
|
|
4971
4087
|
appId,
|
|
4972
4088
|
searchKey,
|
|
4973
|
-
searchEnvVars:
|
|
4974
|
-
language,
|
|
4975
|
-
searchStrategy,
|
|
4976
|
-
appId,
|
|
4977
|
-
searchKey
|
|
4978
|
-
),
|
|
4089
|
+
searchEnvVars: searchEnvVars(language, appId, searchKey),
|
|
4979
4090
|
ingestDir: INGEST_DIR,
|
|
4980
4091
|
ingestionSource,
|
|
4981
4092
|
uploadFilePath,
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
toolchain,
|
|
4986
|
-
verificationLanguages
|
|
4093
|
+
// language.frameworks already prefers the confirm-framework step output,
|
|
4094
|
+
// so the user's confirmed stack (not just raw detection) picks the flavor.
|
|
4095
|
+
uiFramework: detectUiFramework(language)
|
|
4987
4096
|
};
|
|
4988
|
-
const searchToolchain = !bundlesJavaScript(searchStrategy) ? void 0 : ingestionProfile.id === JAVASCRIPT ? toolchain : await resolveToolchain(worktree, LANGUAGE_PROFILES[JAVASCRIPT]);
|
|
4989
|
-
const toolchainForUseCase = (useCase) => useCase === "search" ? searchToolchain : toolchain;
|
|
4990
4097
|
const summaries = [];
|
|
4991
4098
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
4992
4099
|
let agentRuns = 0;
|
|
4100
|
+
let ingestRuntime;
|
|
4993
4101
|
let ingestEntrypoint;
|
|
4994
4102
|
let ingestScriptRan = false;
|
|
4995
4103
|
let ingestRecordCount;
|
|
4996
4104
|
let ingestDurationMs;
|
|
4997
|
-
|
|
4105
|
+
let installFailed = false;
|
|
4998
4106
|
let ingestOutcomeMessage;
|
|
4999
4107
|
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
5000
4108
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
@@ -5008,19 +4116,16 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
5008
4116
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
5009
4117
|
outputSchema: implementationOutputSchema
|
|
5010
4118
|
});
|
|
5011
|
-
const useCaseToolchain = toolchainForUseCase(currentUseCase);
|
|
5012
|
-
if (!useCaseToolchain) return result;
|
|
5013
4119
|
ctx.notify({
|
|
5014
4120
|
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
5015
4121
|
});
|
|
5016
4122
|
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
5017
|
-
useCase: currentUseCase
|
|
5018
|
-
language: useCaseToolchain.profile.id
|
|
4123
|
+
useCase: currentUseCase
|
|
5019
4124
|
});
|
|
5020
|
-
const install = await installWorktreeDeps(worktree
|
|
4125
|
+
const install = await installWorktreeDeps(worktree);
|
|
5021
4126
|
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
5022
4127
|
if (!install.ok) {
|
|
5023
|
-
|
|
4128
|
+
installFailed = true;
|
|
5024
4129
|
logger.warn(
|
|
5025
4130
|
{ useCase: currentUseCase, output: install.output },
|
|
5026
4131
|
"implement: dependency install in worktree failed; generated commands may not run until deps are installed"
|
|
@@ -5034,16 +4139,15 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
5034
4139
|
return runAgent({
|
|
5035
4140
|
instructions: buildAgentInstructions("verification", input),
|
|
5036
4141
|
tools: toolsForUseCase("verification"),
|
|
5037
|
-
outputSchema: verificationOutputSchema
|
|
5038
|
-
// So verifyImplementation runs this repo's checks, not just npm scripts.
|
|
5039
|
-
languages: input.verificationLanguages
|
|
4142
|
+
outputSchema: verificationOutputSchema
|
|
5040
4143
|
});
|
|
5041
4144
|
}
|
|
5042
4145
|
if (useCases.includes("ingestion")) {
|
|
5043
|
-
const { summary, entrypoint } = await runImplementationUseCase("ingestion");
|
|
4146
|
+
const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
|
|
5044
4147
|
summaries.push(formatSummary("ingestion", summary));
|
|
4148
|
+
ingestRuntime = runtime;
|
|
5045
4149
|
ingestEntrypoint = entrypoint;
|
|
5046
|
-
if (
|
|
4150
|
+
if (ingestRuntime && ingestEntrypoint && !installFailed) {
|
|
5047
4151
|
ctx.clearNotices();
|
|
5048
4152
|
const runNow = await ctx.requestUserInput({
|
|
5049
4153
|
prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
|
|
@@ -5056,13 +4160,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
5056
4160
|
const writeKey = await resolveWriteKey(targetIndex);
|
|
5057
4161
|
ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
|
|
5058
4162
|
const scriptLogId = ctx.logStart("runIngestScript", {
|
|
5059
|
-
|
|
4163
|
+
runtime: ingestRuntime,
|
|
5060
4164
|
entrypoint: ingestEntrypoint
|
|
5061
4165
|
});
|
|
5062
4166
|
const startedAt = Date.now();
|
|
5063
4167
|
const run = await runIngestScript(
|
|
5064
4168
|
worktree,
|
|
5065
|
-
|
|
4169
|
+
ingestRuntime,
|
|
5066
4170
|
ingestEntrypoint,
|
|
5067
4171
|
{
|
|
5068
4172
|
[APP_ID_VAR]: ingestApp.id,
|
|
@@ -5076,7 +4180,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
5076
4180
|
ingestRecordCount = parseIngestRecordCount(run.output);
|
|
5077
4181
|
if (ingestRecordCount != null) {
|
|
5078
4182
|
track("AI Wizard Ingest Successful", {
|
|
5079
|
-
entity_name:
|
|
4183
|
+
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
5080
4184
|
record_count: ingestRecordCount,
|
|
5081
4185
|
duration_ms: ingestDurationMs
|
|
5082
4186
|
});
|
|
@@ -5089,7 +4193,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
5089
4193
|
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run.reason}`;
|
|
5090
4194
|
logger.warn(
|
|
5091
4195
|
{
|
|
5092
|
-
|
|
4196
|
+
runtime: ingestRuntime,
|
|
5093
4197
|
entrypoint: ingestEntrypoint,
|
|
5094
4198
|
reason: run.reason
|
|
5095
4199
|
},
|
|
@@ -5112,7 +4216,7 @@ ${run.output}` : status;
|
|
|
5112
4216
|
outcomeMessage = `\u274C Ingestion failed.${run.output ? ` ${run.output}` : ""}`;
|
|
5113
4217
|
logger.warn(
|
|
5114
4218
|
{
|
|
5115
|
-
|
|
4219
|
+
runtime: ingestRuntime,
|
|
5116
4220
|
entrypoint: ingestEntrypoint,
|
|
5117
4221
|
output: run.output
|
|
5118
4222
|
},
|
|
@@ -5129,15 +4233,10 @@ ${run.output}` : status;
|
|
|
5129
4233
|
}
|
|
5130
4234
|
}
|
|
5131
4235
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
5132
|
-
if (ingestEntrypoint) {
|
|
4236
|
+
if (ingestRuntime && ingestEntrypoint) {
|
|
5133
4237
|
commandMessages.push(
|
|
5134
|
-
`Ingestion command: ${buildIngestCommand(worktree,
|
|
4238
|
+
`Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
|
|
5135
4239
|
);
|
|
5136
|
-
if (toolchain.ingest.kind === "manual") {
|
|
5137
|
-
commandMessages.push(
|
|
5138
|
-
`The wizard does not run ${ingestionProfile.displayName} ${toolchain.packageManager.id} projects \u2014 run the command above yourself to ingest.`
|
|
5139
|
-
);
|
|
5140
|
-
}
|
|
5141
4240
|
}
|
|
5142
4241
|
await ctx.requestUserInput({
|
|
5143
4242
|
// No question being asked here, just an acknowledgement — the
|
|
@@ -5148,20 +4247,7 @@ ${run.output}` : status;
|
|
|
5148
4247
|
messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
|
|
5149
4248
|
});
|
|
5150
4249
|
}
|
|
5151
|
-
|
|
5152
|
-
if (skipSearch) {
|
|
5153
|
-
const target = describeSearchTarget(
|
|
5154
|
-
input.searchStrategy,
|
|
5155
|
-
input.frameworkName
|
|
5156
|
-
);
|
|
5157
|
-
summaries.push(
|
|
5158
|
-
`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).`
|
|
5159
|
-
);
|
|
5160
|
-
track("AI Wizard Search UI Skipped", {
|
|
5161
|
-
framework: input.frameworkName ?? "unknown"
|
|
5162
|
-
});
|
|
5163
|
-
}
|
|
5164
|
-
if (useCases.includes("search") && !skipSearch) {
|
|
4250
|
+
if (useCases.includes("search")) {
|
|
5165
4251
|
let extraInstructions = [];
|
|
5166
4252
|
const preSearchFiles = new Set(await listChangedFiles(worktree));
|
|
5167
4253
|
for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
|
|
@@ -5232,9 +4318,9 @@ ${run.output}` : status;
|
|
|
5232
4318
|
"implement: agent reported success but no files changed in the worktree"
|
|
5233
4319
|
);
|
|
5234
4320
|
}
|
|
5235
|
-
if (
|
|
4321
|
+
if (installFailed) {
|
|
5236
4322
|
summaries.push(
|
|
5237
|
-
|
|
4323
|
+
'\u26A0\uFE0F Dependency install in the worktree failed. Run your package manager install in the worktree before the command below, or it will fail with "Cannot find module".'
|
|
5238
4324
|
);
|
|
5239
4325
|
}
|
|
5240
4326
|
return {
|
|
@@ -5242,10 +4328,10 @@ ${run.output}` : status;
|
|
|
5242
4328
|
filesChanged,
|
|
5243
4329
|
summary: summaries.join("\n\n"),
|
|
5244
4330
|
worktreePath: worktree,
|
|
5245
|
-
...useCases.includes("ingestion") && ingestEntrypoint ? {
|
|
4331
|
+
...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
|
|
5246
4332
|
ingestCommand: buildIngestCommand(
|
|
5247
4333
|
worktree,
|
|
5248
|
-
|
|
4334
|
+
ingestRuntime,
|
|
5249
4335
|
ingestEntrypoint
|
|
5250
4336
|
),
|
|
5251
4337
|
ingestScriptRan,
|