@algolia/wizard 0.19.0 → 0.21.0-rc.111.195
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/README.md +0 -14
- package/dist/main.js +402 -488
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,22 +12,8 @@ npx @algolia/wizard
|
|
|
12
12
|
|
|
13
13
|
# run a specific workflow by id
|
|
14
14
|
npx @algolia/wizard <workflow-id>
|
|
15
|
-
|
|
16
|
-
# see all options
|
|
17
|
-
npx @algolia/wizard --help
|
|
18
15
|
```
|
|
19
16
|
|
|
20
|
-
### Options
|
|
21
|
-
|
|
22
|
-
| Flag | Effect |
|
|
23
|
-
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
24
|
-
| `--seed <step-id>` | Start the workflow at the step with this id (e.g. `--seed ingestion`), with the earlier steps pre-filled with test data. Pass with no value to print the step ids. |
|
|
25
|
-
| `--no-telemetry` | Send no telemetry or analytics for this run. |
|
|
26
|
-
| `--reset-on-run` | Wipe this project's wizard state (run state, AI-changes consent, worktrees) before starting, so the run behaves like a first-ever run. Credentials are untouched. |
|
|
27
|
-
| `-h`, `--help` | Print usage. |
|
|
28
|
-
|
|
29
|
-
`--seed` pre-fills the earlier steps with fabricated data so a single step can be exercised without running the whole workflow — useful for testing a step, not for a real implementation. It replaces any in-progress run for that workflow and pre-grants the AI-changes consent. See [Starting mid-workflow](CONTRIBUTING.md#starting-mid-workflow---seed).
|
|
30
|
-
|
|
31
17
|
On first run, Wizard checks whether you're signed in to the Algolia CLI. If you aren't, it runs `algolia auth login --non-interactive` — the browser opens for sign-in, and no prompts land in the wizard's terminal. It then asks which Algolia application to work in (skipping the question when the account has only one) and makes it current with `algolia application select`.
|
|
32
18
|
|
|
33
19
|
You'll also be asked once to consent to AI-authored changes to the repository.
|
package/dist/main.js
CHANGED
|
@@ -251,6 +251,7 @@ var useWizard = create((set, get) => ({
|
|
|
251
251
|
_noticeTimer: null,
|
|
252
252
|
cliOutput: [],
|
|
253
253
|
targetIndex: null,
|
|
254
|
+
writtenFiles: [],
|
|
254
255
|
logs: [],
|
|
255
256
|
error: null,
|
|
256
257
|
inputReq: null,
|
|
@@ -344,6 +345,8 @@ var useWizard = create((set, get) => ({
|
|
|
344
345
|
})),
|
|
345
346
|
clearCliOutput: () => set({ cliOutput: [] }),
|
|
346
347
|
setTargetIndex: (index) => set({ targetIndex: index }),
|
|
348
|
+
recordWrittenFile: (path) => set((s) => ({ writtenFiles: [...s.writtenFiles, path] })),
|
|
349
|
+
clearWrittenFiles: () => set({ writtenFiles: [] }),
|
|
347
350
|
logStart: (kind, name, input) => {
|
|
348
351
|
const id = nanoid();
|
|
349
352
|
set((s) => ({
|
|
@@ -391,6 +394,7 @@ var useWizard = create((set, get) => ({
|
|
|
391
394
|
notices: [],
|
|
392
395
|
cliOutput: [],
|
|
393
396
|
targetIndex: null,
|
|
397
|
+
writtenFiles: [],
|
|
394
398
|
logs: [],
|
|
395
399
|
error: null,
|
|
396
400
|
inputReq: null,
|
|
@@ -1200,7 +1204,7 @@ var accessItems = [
|
|
|
1200
1204
|
{
|
|
1201
1205
|
tag: "WRITE",
|
|
1202
1206
|
title: "Code changes",
|
|
1203
|
-
description: "creates & edits files (search UI, config) in
|
|
1207
|
+
description: "creates & edits files (search UI, config) directly in your branch."
|
|
1204
1208
|
},
|
|
1205
1209
|
{
|
|
1206
1210
|
tag: "EXEC",
|
|
@@ -1215,7 +1219,7 @@ var accessItems = [
|
|
|
1215
1219
|
{
|
|
1216
1220
|
tag: "KEY",
|
|
1217
1221
|
title: "Credentials",
|
|
1218
|
-
description: "writes your Algolia app id and a search-only key (safe to expose) to .env in
|
|
1222
|
+
description: "writes your Algolia app id and a search-only key (safe to expose) to .env in your project."
|
|
1219
1223
|
}
|
|
1220
1224
|
];
|
|
1221
1225
|
var neverItems = [
|
|
@@ -2246,7 +2250,7 @@ async function ensureApplication() {
|
|
|
2246
2250
|
}
|
|
2247
2251
|
|
|
2248
2252
|
// src/workflows/default.ts
|
|
2249
|
-
import { z as
|
|
2253
|
+
import { z as z30 } from "zod";
|
|
2250
2254
|
|
|
2251
2255
|
// src/actions/listIndices.ts
|
|
2252
2256
|
import { z as z5 } from "zod";
|
|
@@ -2496,6 +2500,7 @@ function writeFileTool(ctx) {
|
|
|
2496
2500
|
}
|
|
2497
2501
|
await mkdir3(dirname4(resolved2.target), { recursive: true });
|
|
2498
2502
|
await writeFile3(resolved2.target, content, "utf8");
|
|
2503
|
+
useWizard.getState().recordWrittenFile(resolved2.target);
|
|
2499
2504
|
return `Wrote to ${filePath}`;
|
|
2500
2505
|
} catch (err) {
|
|
2501
2506
|
return `Error writing ${filePath}: ${err.message}`;
|
|
@@ -2767,6 +2772,28 @@ async function ensureGitIgnored(root, target) {
|
|
|
2767
2772
|
var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
|
|
2768
2773
|
var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
|
|
2769
2774
|
var INDEX_NAME_VAR = "ALGOLIA_INDEX_NAME";
|
|
2775
|
+
var PUBLIC_APP_ID_SUFFIX = "ALGOLIA_APP_ID";
|
|
2776
|
+
var PUBLIC_SEARCH_KEY_SUFFIX = "ALGOLIA_SEARCH_KEY";
|
|
2777
|
+
var PUBLIC_INDEX_NAME_SUFFIX = "ALGOLIA_INDEX_NAME";
|
|
2778
|
+
function publicAppIdVar(prefix) {
|
|
2779
|
+
return `${prefix}${PUBLIC_APP_ID_SUFFIX}`;
|
|
2780
|
+
}
|
|
2781
|
+
function publicSearchKeyVar(prefix) {
|
|
2782
|
+
return `${prefix}${PUBLIC_SEARCH_KEY_SUFFIX}`;
|
|
2783
|
+
}
|
|
2784
|
+
function publicIndexNameVar(prefix) {
|
|
2785
|
+
return `${prefix}${PUBLIC_INDEX_NAME_SUFFIX}`;
|
|
2786
|
+
}
|
|
2787
|
+
function publicSearchEnvVars(prefix, index, appId, searchKey) {
|
|
2788
|
+
return [
|
|
2789
|
+
{ name: publicAppIdVar(prefix), value: appId ?? "<your-algolia-app-id>" },
|
|
2790
|
+
{
|
|
2791
|
+
name: publicSearchKeyVar(prefix),
|
|
2792
|
+
value: searchKey ?? "<your-algolia-search-only-api-key>"
|
|
2793
|
+
},
|
|
2794
|
+
{ name: publicIndexNameVar(prefix), value: index }
|
|
2795
|
+
];
|
|
2796
|
+
}
|
|
2770
2797
|
function appendEnv(content, entries) {
|
|
2771
2798
|
const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
2772
2799
|
const lines = entries.map(([name, value]) => `${name}=${value}
|
|
@@ -3246,7 +3273,7 @@ var anthropic = createAnthropic({
|
|
|
3246
3273
|
});
|
|
3247
3274
|
function generateRecordTool(ctx) {
|
|
3248
3275
|
return tool10({
|
|
3249
|
-
description: "Generate realistic sample records for an entity and write them to a JSON file
|
|
3276
|
+
description: "Generate realistic sample records for an entity and write them to a JSON file. Provide the entity name and its attributes; this tool asks a model to invent varied, realistic values, each with a unique objectID, and returns the file path to read them from at runtime. Do not invent the record values or objectIDs yourself, and do not inline the returned records into the script \u2014 call this tool and read the file it writes.",
|
|
3250
3277
|
inputSchema: z17.object({
|
|
3251
3278
|
entityName: z17.string().describe("Name of the entity to generate records for."),
|
|
3252
3279
|
attributes: z17.array(z17.string()).describe("Attribute names each record must contain."),
|
|
@@ -3500,7 +3527,10 @@ async function runAgent(req) {
|
|
|
3500
3527
|
import z21 from "zod";
|
|
3501
3528
|
var detectLanguageSchema = z21.object({
|
|
3502
3529
|
languages: z21.array(z21.object({ name: z21.string(), version: z21.string() })),
|
|
3503
|
-
frameworks: z21.array(z21.object({ name: z21.string(), version: z21.string() }))
|
|
3530
|
+
frameworks: z21.array(z21.object({ name: z21.string(), version: z21.string() })),
|
|
3531
|
+
publicEnvVarPrefix: z21.string().regex(/^([A-Z0-9]+_)*$/).describe(
|
|
3532
|
+
`The prefix a bundler/framework requires for an env var to reach client-side code (e.g. "NEXT_PUBLIC_", "VITE_", "NUXT_PUBLIC_"), uppercase with a trailing underscore when non-empty. Prefer the project's own existing convention over a framework default. Empty string when the project has no client-side env var exposure (e.g. a backend-only project).`
|
|
3533
|
+
)
|
|
3504
3534
|
});
|
|
3505
3535
|
var detectLanguage = () => runAgent({
|
|
3506
3536
|
instructions: [
|
|
@@ -3509,6 +3539,7 @@ var detectLanguage = () => runAgent({
|
|
|
3509
3539
|
"If a meta-framework is used, exclude the framework it builds on (e.g. Next.js over React, Rails over Rack).",
|
|
3510
3540
|
"Return the exact version",
|
|
3511
3541
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
3542
|
+
`Determine publicEnvVarPrefix: check the project's own env var usage first (e.g. names already referenced in code, .env/.env.example); if none exists, fall back to the detected framework's known convention for exposing env vars to client-side code; use "" when the project has no such convention (e.g. a backend-only project).`,
|
|
3512
3543
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
3513
3544
|
"When done, call reportStatus"
|
|
3514
3545
|
],
|
|
@@ -3604,7 +3635,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3604
3635
|
// package.json
|
|
3605
3636
|
var package_default = {
|
|
3606
3637
|
name: "@algolia/wizard",
|
|
3607
|
-
version: "0.
|
|
3638
|
+
version: "0.21.0-rc.111.195",
|
|
3608
3639
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3609
3640
|
type: "module",
|
|
3610
3641
|
engines: {
|
|
@@ -3971,11 +4002,10 @@ ${JSON.stringify(s.output, null, 2)}`
|
|
|
3971
4002
|
function formatReviewSummary(result) {
|
|
3972
4003
|
const nextStepLines = result.nextSteps.map((step) => {
|
|
3973
4004
|
const isIngestCommand = step.includes(".algolia-wizard/ingest.sh");
|
|
3974
|
-
const isWorktreeCommand = step.includes("/worktrees/");
|
|
3975
4005
|
return {
|
|
3976
4006
|
text: `\u2192 ${step}`,
|
|
3977
|
-
color: isIngestCommand ? COLORS.brand :
|
|
3978
|
-
bold: isIngestCommand
|
|
4007
|
+
color: isIngestCommand ? COLORS.brand : void 0,
|
|
4008
|
+
bold: isIngestCommand
|
|
3979
4009
|
};
|
|
3980
4010
|
});
|
|
3981
4011
|
return [
|
|
@@ -4009,16 +4039,14 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
4009
4039
|
};
|
|
4010
4040
|
|
|
4011
4041
|
// src/actions/implement.ts
|
|
4012
|
-
import
|
|
4013
|
-
import { join as join12 } from "node:path";
|
|
4042
|
+
import z29 from "zod";
|
|
4043
|
+
import { join as join12, relative as relative6 } from "node:path";
|
|
4014
4044
|
|
|
4015
|
-
// src/lib/
|
|
4045
|
+
// src/lib/git.ts
|
|
4016
4046
|
import { execFile as execFile2 } from "node:child_process";
|
|
4017
|
-
import { copyFile, mkdir as mkdir6,
|
|
4047
|
+
import { copyFile, mkdir as mkdir6, readFile as readFile8, stat as stat3, writeFile as writeFile7 } from "node:fs/promises";
|
|
4018
4048
|
import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join10, resolve as resolve3 } from "node:path";
|
|
4019
4049
|
var MAX_BUFFER = 32 * 1024 * 1024;
|
|
4020
|
-
var MAX_WIZARD_WORKTREES = 3;
|
|
4021
|
-
var WIZARD_BRANCH_PREFIX = "wizard/implement-";
|
|
4022
4050
|
function git(args) {
|
|
4023
4051
|
return new Promise((resolve4, reject) => {
|
|
4024
4052
|
execFile2("git", args, { maxBuffer: MAX_BUFFER }, (err, stdout, stderr) => {
|
|
@@ -4041,44 +4069,7 @@ async function assertGitRepoWithHead(repoRoot) {
|
|
|
4041
4069
|
);
|
|
4042
4070
|
}
|
|
4043
4071
|
}
|
|
4044
|
-
async function
|
|
4045
|
-
const out = await git(["-C", repoRoot, "status", "--porcelain"]);
|
|
4046
|
-
return out.trim().length > 0;
|
|
4047
|
-
}
|
|
4048
|
-
async function pruneOldWorktrees(repoRoot) {
|
|
4049
|
-
const dir = join10(stateDir(repoRoot), "worktrees");
|
|
4050
|
-
const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
4051
|
-
for (const slug of stale) {
|
|
4052
|
-
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
4053
|
-
try {
|
|
4054
|
-
await git([
|
|
4055
|
-
"-C",
|
|
4056
|
-
repoRoot,
|
|
4057
|
-
"worktree",
|
|
4058
|
-
"remove",
|
|
4059
|
-
"--force",
|
|
4060
|
-
join10(dir, slug)
|
|
4061
|
-
]);
|
|
4062
|
-
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
4063
|
-
} catch (err) {
|
|
4064
|
-
logger.warn(
|
|
4065
|
-
{ branch, err: err.message },
|
|
4066
|
-
"createWorktree: failed to prune a stale wizard worktree; continuing"
|
|
4067
|
-
);
|
|
4068
|
-
}
|
|
4069
|
-
}
|
|
4070
|
-
}
|
|
4071
|
-
async function createWorktree(repoRoot) {
|
|
4072
|
-
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
4073
|
-
const dirSlug = branch.replace(/\//g, "-");
|
|
4074
|
-
const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
|
|
4075
|
-
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
4076
|
-
await pruneOldWorktrees(repoRoot);
|
|
4077
|
-
await mkdir6(dirname7(path), { recursive: true });
|
|
4078
|
-
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
4079
|
-
return { path, branch };
|
|
4080
|
-
}
|
|
4081
|
-
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
4072
|
+
async function copyUploadIntoProject(repoRoot, ingestDir, sourcePath) {
|
|
4082
4073
|
const trimmed = sourcePath.trim();
|
|
4083
4074
|
if (!trimmed) {
|
|
4084
4075
|
return { ok: false, reason: "no file path was provided" };
|
|
@@ -4092,7 +4083,10 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
4092
4083
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
4093
4084
|
}
|
|
4094
4085
|
const relPath = join10(ingestDir, basename2(source));
|
|
4095
|
-
const dest = join10(
|
|
4086
|
+
const dest = join10(repoRoot, relPath);
|
|
4087
|
+
if (resolve3(source) === resolve3(dest)) {
|
|
4088
|
+
return { ok: true, relPath };
|
|
4089
|
+
}
|
|
4096
4090
|
try {
|
|
4097
4091
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
4098
4092
|
await copyFile(source, dest);
|
|
@@ -4107,10 +4101,10 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
4107
4101
|
function hasEnvVar(content, name) {
|
|
4108
4102
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
4109
4103
|
}
|
|
4110
|
-
async function readEnvVar(
|
|
4104
|
+
async function readEnvVar(repoRoot, name) {
|
|
4111
4105
|
let content;
|
|
4112
4106
|
try {
|
|
4113
|
-
content = await readFile8(join10(
|
|
4107
|
+
content = await readFile8(join10(repoRoot, ".env"), "utf8");
|
|
4114
4108
|
} catch (err) {
|
|
4115
4109
|
if (err.code !== "ENOENT") throw err;
|
|
4116
4110
|
return void 0;
|
|
@@ -4124,8 +4118,8 @@ async function readEnvVar(worktreePath, name) {
|
|
|
4124
4118
|
if (!value || value.startsWith("<")) return void 0;
|
|
4125
4119
|
return value;
|
|
4126
4120
|
}
|
|
4127
|
-
async function writeSearchEnvValues(
|
|
4128
|
-
const target = join10(
|
|
4121
|
+
async function writeSearchEnvValues(repoRoot, vars) {
|
|
4122
|
+
const target = join10(repoRoot, ".env");
|
|
4129
4123
|
let existing = "";
|
|
4130
4124
|
try {
|
|
4131
4125
|
existing = await readFile8(target, "utf8");
|
|
@@ -4140,61 +4134,27 @@ async function writeSearchEnvValues(worktreePath, vars) {
|
|
|
4140
4134
|
await writeFile7(target, existing + prefix + lines, "utf8");
|
|
4141
4135
|
return missing.map((v) => v.name);
|
|
4142
4136
|
}
|
|
4143
|
-
async function listChangedFiles(worktreePath) {
|
|
4144
|
-
const raw = await git(["-C", worktreePath, "status", "--porcelain", "-z"]);
|
|
4145
|
-
const entries = raw.split("\0");
|
|
4146
|
-
const files = [];
|
|
4147
|
-
for (let i = 0; i < entries.length; i += 1) {
|
|
4148
|
-
const entry = entries[i];
|
|
4149
|
-
if (!entry) continue;
|
|
4150
|
-
files.push(entry.slice(3));
|
|
4151
|
-
if (["R", "C"].includes(entry[0]) || ["R", "C"].includes(entry[1])) i += 1;
|
|
4152
|
-
}
|
|
4153
|
-
return files;
|
|
4154
|
-
}
|
|
4155
4137
|
function normalizeFindingPaths(findings) {
|
|
4156
4138
|
return {
|
|
4157
4139
|
...findings,
|
|
4158
4140
|
ingestionAnalysis: findings.ingestionAnalysis?.map((e) => ({
|
|
4159
4141
|
...e,
|
|
4160
|
-
paths: e.paths.map(
|
|
4142
|
+
paths: e.paths.map(toRootRelative)
|
|
4161
4143
|
})),
|
|
4162
4144
|
searchImplementationAnalysis: findings.searchImplementationAnalysis ? normalizeSearchLocation(findings.searchImplementationAnalysis) : void 0,
|
|
4163
4145
|
confirmedEntities: findings.confirmedEntities?.map((e) => ({
|
|
4164
4146
|
...e,
|
|
4165
|
-
paths: e.paths.map(
|
|
4147
|
+
paths: e.paths.map(toRootRelative)
|
|
4166
4148
|
}))
|
|
4167
4149
|
};
|
|
4168
4150
|
}
|
|
4169
4151
|
function normalizeSearchLocation(path) {
|
|
4170
|
-
const normalized = path ?
|
|
4152
|
+
const normalized = path ? toRootRelative(path).trim() : "";
|
|
4171
4153
|
return normalized && normalized.toLowerCase() !== "unknown" ? normalized : void 0;
|
|
4172
4154
|
}
|
|
4173
|
-
function
|
|
4155
|
+
function toRootRelative(p) {
|
|
4174
4156
|
return p.replace(/^\/+/, "");
|
|
4175
4157
|
}
|
|
4176
|
-
async function confirmDirtyWorkingTree(ctx, repoRoot) {
|
|
4177
|
-
const MAX_LISTED_DIRTY_FILES = 10;
|
|
4178
|
-
const dirty = await listChangedFiles(repoRoot);
|
|
4179
|
-
const shown = dirty.slice(0, MAX_LISTED_DIRTY_FILES);
|
|
4180
|
-
const overflow = dirty.length - shown.length;
|
|
4181
|
-
const answer = await ctx.requestUserInput({
|
|
4182
|
-
prompt: "Proceed using HEAD only? Uncommitted changes will NOT be included in the generated implementation.",
|
|
4183
|
-
promptType: "acceptReject",
|
|
4184
|
-
options: [],
|
|
4185
|
-
messages: [
|
|
4186
|
-
`${dirty.length} uncommitted change(s) detected. The wizard builds an isolated worktree from HEAD, so these are ignored:`,
|
|
4187
|
-
...shown.map((file) => ` \u2022 ${file}`),
|
|
4188
|
-
...overflow > 0 ? [` \u2022 \u2026and ${overflow} more`] : [],
|
|
4189
|
-
"Commit or stash them first to include them in the implementation."
|
|
4190
|
-
]
|
|
4191
|
-
});
|
|
4192
|
-
if (answer !== true) {
|
|
4193
|
-
throw new Error(
|
|
4194
|
-
"implement aborted: commit or stash your changes so they are built into the worktree, then re-run the wizard."
|
|
4195
|
-
);
|
|
4196
|
-
}
|
|
4197
|
-
}
|
|
4198
4158
|
|
|
4199
4159
|
// src/lib/algoliaDocs.ts
|
|
4200
4160
|
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
@@ -4254,36 +4214,46 @@ function getFrameworkSpecificDoc(frameworks) {
|
|
|
4254
4214
|
return loadAlgoliaDoc("js");
|
|
4255
4215
|
}
|
|
4256
4216
|
|
|
4257
|
-
// src/
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4217
|
+
// src/actions/resolveEnvVarPrefix.ts
|
|
4218
|
+
import z28 from "zod";
|
|
4219
|
+
var resolveEnvVarPrefixSchema = z28.object({
|
|
4220
|
+
publicEnvVarPrefix: detectLanguageSchema.shape.publicEnvVarPrefix
|
|
4221
|
+
});
|
|
4222
|
+
var resolveEnvVarPrefix = (frameworkName) => runAgent({
|
|
4223
|
+
instructions: [
|
|
4224
|
+
`The developer corrected the project's framework to "${frameworkName}".`,
|
|
4225
|
+
`Determine publicEnvVarPrefix for this framework: check the project's own env var usage first (e.g. names already referenced in code, .env/.env.example); if none exists, fall back to this framework's known convention for exposing env vars to client-side code; use "" when the framework has no such convention (e.g. a backend-only framework).`,
|
|
4226
|
+
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
4227
|
+
"When done, call reportStatus"
|
|
4228
|
+
],
|
|
4229
|
+
tools: ["listFiles", "changeDirectory", "readFile", "searchFiles"],
|
|
4230
|
+
outputSchema: resolveEnvVarPrefixSchema,
|
|
4231
|
+
modelSize: "small"
|
|
4232
|
+
});
|
|
4261
4233
|
|
|
4262
4234
|
// src/actions/implement.ts
|
|
4263
|
-
var implementSchema =
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
name: z28.string(),
|
|
4275
|
-
value: z28.string()
|
|
4235
|
+
var implementSchema = z29.object({
|
|
4236
|
+
summary: z29.string(),
|
|
4237
|
+
ingestCommand: z29.string().optional(),
|
|
4238
|
+
ingestScriptRan: z29.boolean().optional(),
|
|
4239
|
+
ingestRecordCount: z29.number().optional(),
|
|
4240
|
+
ingestDurationMs: z29.number().optional(),
|
|
4241
|
+
ingestionSource: z29.enum(["local", "fileUpload", "generated"]),
|
|
4242
|
+
searchEnvVars: z29.array(
|
|
4243
|
+
z29.object({
|
|
4244
|
+
name: z29.string(),
|
|
4245
|
+
value: z29.string()
|
|
4276
4246
|
})
|
|
4277
4247
|
).optional()
|
|
4278
4248
|
});
|
|
4279
|
-
var implementationOutputSchema =
|
|
4280
|
-
summary:
|
|
4281
|
-
ingestCommand:
|
|
4249
|
+
var implementationOutputSchema = z29.object({
|
|
4250
|
+
summary: z29.string(),
|
|
4251
|
+
ingestCommand: z29.string().optional()
|
|
4282
4252
|
});
|
|
4283
|
-
var verificationOutputSchema =
|
|
4284
|
-
summary:
|
|
4285
|
-
sufficient:
|
|
4286
|
-
additionalInstructions:
|
|
4253
|
+
var verificationOutputSchema = z29.object({
|
|
4254
|
+
summary: z29.string(),
|
|
4255
|
+
sufficient: z29.boolean(),
|
|
4256
|
+
additionalInstructions: z29.string().optional()
|
|
4287
4257
|
});
|
|
4288
4258
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
4289
4259
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
@@ -4314,57 +4284,8 @@ function searchUiTarget(language) {
|
|
|
4314
4284
|
function frameworksForDoc(language) {
|
|
4315
4285
|
return [matchUiFramework(language)?.doc ?? "js"];
|
|
4316
4286
|
}
|
|
4317
|
-
function publicEnvPrefix(language) {
|
|
4318
|
-
const frameworkNames = lower(language.frameworks);
|
|
4319
|
-
if (frameworkNames.some((name) => name.includes("next"))) {
|
|
4320
|
-
return "NEXT_PUBLIC_";
|
|
4321
|
-
}
|
|
4322
|
-
if (frameworkNames.some((name) => name.includes("nuxt"))) {
|
|
4323
|
-
return "NUXT_PUBLIC_";
|
|
4324
|
-
}
|
|
4325
|
-
if (frameworkNames.some((name) => name.includes("astro"))) {
|
|
4326
|
-
return "PUBLIC_";
|
|
4327
|
-
}
|
|
4328
|
-
if (frameworkNames.some((name) => name.includes("vite"))) {
|
|
4329
|
-
return "VITE_";
|
|
4330
|
-
}
|
|
4331
|
-
return isJsProject(language) ? "PUBLIC_" : "";
|
|
4332
|
-
}
|
|
4333
|
-
var APP_ID_VAR_SUFFIX = "ALGOLIA_APP_ID";
|
|
4334
|
-
var SEARCH_KEY_VAR_SUFFIX = "ALGOLIA_SEARCH_API_KEY";
|
|
4335
|
-
var INDEX_VAR_SUFFIX = "ALGOLIA_INDEX_NAME";
|
|
4336
|
-
function appIdVar(language) {
|
|
4337
|
-
return `${publicEnvPrefix(language)}${APP_ID_VAR_SUFFIX}`;
|
|
4338
|
-
}
|
|
4339
|
-
function searchKeyVar(language) {
|
|
4340
|
-
return `${publicEnvPrefix(language)}${SEARCH_KEY_VAR_SUFFIX}`;
|
|
4341
|
-
}
|
|
4342
|
-
function searchIndexVar(language) {
|
|
4343
|
-
return `${publicEnvPrefix(language)}${INDEX_VAR_SUFFIX}`;
|
|
4344
|
-
}
|
|
4345
|
-
function searchEnvVars(language, index, appId, searchKey) {
|
|
4346
|
-
return [
|
|
4347
|
-
{
|
|
4348
|
-
name: appIdVar(language),
|
|
4349
|
-
value: appId ?? "<your-algolia-app-id>"
|
|
4350
|
-
},
|
|
4351
|
-
{
|
|
4352
|
-
name: searchKeyVar(language),
|
|
4353
|
-
value: searchKey ?? "<your-algolia-search-only-api-key>"
|
|
4354
|
-
},
|
|
4355
|
-
// Wizard-supplied rather than written into the generated code, because an
|
|
4356
|
-
// agent that retypes the name (appending the project name, re-casing it)
|
|
4357
|
-
// leaves the UI querying an index that does not exist.
|
|
4358
|
-
{
|
|
4359
|
-
name: searchIndexVar(language),
|
|
4360
|
-
value: index
|
|
4361
|
-
}
|
|
4362
|
-
];
|
|
4363
|
-
}
|
|
4364
4287
|
function baseInstructions(input) {
|
|
4365
4288
|
return [
|
|
4366
|
-
// Agents have renamed this (e.g. appending the project name), which the
|
|
4367
|
-
// index-scoped keys then reject with a 403.
|
|
4368
4289
|
`Target Algolia index, to be used exactly as written \u2014 never renamed, re-cased, prefixed, or suffixed: "${input.targetIndex}"`,
|
|
4369
4290
|
`Project languages and frameworks: ${JSON.stringify(input.language)}`,
|
|
4370
4291
|
"Make minimal, idiomatic changes; do not touch unrelated code.",
|
|
@@ -4382,14 +4303,14 @@ function sourceSpecificInstructions(input) {
|
|
|
4382
4303
|
"Add env vars for any local source access (DB URL, API base, file paths) that is not a fixed repo path."
|
|
4383
4304
|
],
|
|
4384
4305
|
fileUpload: [
|
|
4385
|
-
`Records come from the developer's file, already copied into the
|
|
4306
|
+
`Records come from the developer's file, already copied into the project at "${input.uploadFilePath}". Read and parse that exact file.`,
|
|
4386
4307
|
"Parse by extension: JSON = array of objects; CSV/TSV = header row maps to keys.",
|
|
4387
4308
|
"Map parsed columns/fields to the confirmed entity attributes.",
|
|
4388
4309
|
"Never fabricate, hardcode, or substitute a different file."
|
|
4389
4310
|
],
|
|
4390
4311
|
generated: [
|
|
4391
4312
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
4392
|
-
"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
|
|
4313
|
+
"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, returning the file path. Do not write records or objectIDs yourself.",
|
|
4393
4314
|
"In the script, read and parse each returned file path at runtime using your language's standard JSON support, instead of inlining the records as literals.",
|
|
4394
4315
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
4395
4316
|
]
|
|
@@ -4436,16 +4357,13 @@ function searchInstructions(input) {
|
|
|
4436
4357
|
],
|
|
4437
4358
|
`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 search input and results list against the target index.`,
|
|
4438
4359
|
"If a search box already exists, replace it with yours.",
|
|
4439
|
-
`Read the index name from the ${
|
|
4360
|
+
`Read the index name from the ${publicIndexNameVar(input.publicEnvVarPrefix)} env var, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or component name.`,
|
|
4440
4361
|
"Read the App ID, the search-only API key, and the index name from env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
4441
|
-
// The key is provisioned only after verification passes,
|
|
4442
|
-
//
|
|
4443
|
-
//
|
|
4444
|
-
// would be reused as if it were real.
|
|
4362
|
+
// The key is provisioned only after verification passes, and the wizard
|
|
4363
|
+
// reads .env to decide whether a key already exists — an agent-invented
|
|
4364
|
+
// value there would be reused as if it were real.
|
|
4445
4365
|
`Add Algolia App ID "${input.appId}"; leave the search-only key as a placeholder. Do not create or edit .env \u2014 the wizard writes the resolved key there itself.`,
|
|
4446
|
-
//
|
|
4447
|
-
// ".env" right after this step, so a renamed prefix would leave the code
|
|
4448
|
-
// reading a var the wizard never wrote.
|
|
4366
|
+
// The wizard writes these exact names into .env right after this step.
|
|
4449
4367
|
`Use exactly these env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
4450
4368
|
"Install any Algolia packages you import with the project's own package manager via runShell, and declare them in the project's dependency manifest.",
|
|
4451
4369
|
"Match the styles of the application as closely as possible.",
|
|
@@ -4454,10 +4372,10 @@ function searchInstructions(input) {
|
|
|
4454
4372
|
}
|
|
4455
4373
|
function verificationInstructions(input) {
|
|
4456
4374
|
return [
|
|
4457
|
-
"Verify the Algolia implementation changes
|
|
4375
|
+
"Verify the Algolia implementation changes.",
|
|
4458
4376
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
4459
4377
|
"Run the project's own checks (lint, type check, tests) via runShell, using the commands the project actually defines \u2014 its task runner, manifest scripts, or Makefile. Run every check that applies, not just the first.",
|
|
4460
|
-
"
|
|
4378
|
+
"If a check fails because packages or modules are missing, install the dependencies via runShell and re-run it rather than changing the code.",
|
|
4461
4379
|
"For issues caused by the implementation, make minimal fixes with writeFile and re-run the checks.",
|
|
4462
4380
|
"Do not make speculative fixes when no checks exist, a check cannot run, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
|
|
4463
4381
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
@@ -4552,11 +4470,11 @@ function ingestFailure(attempt, executions) {
|
|
|
4552
4470
|
}
|
|
4553
4471
|
return { reason: `it failed (exit ${attempt.exitCode ?? "unknown"})`, detail };
|
|
4554
4472
|
}
|
|
4555
|
-
function makeToolContext(
|
|
4473
|
+
function makeToolContext(root, env = async () => ({})) {
|
|
4556
4474
|
return createToolContext(
|
|
4557
4475
|
DEFAULT_TOOL_LIMITS,
|
|
4558
|
-
|
|
4559
|
-
createShellContext({ env, approve: storeApproval(
|
|
4476
|
+
root,
|
|
4477
|
+
createShellContext({ env, approve: storeApproval(root) })
|
|
4560
4478
|
);
|
|
4561
4479
|
}
|
|
4562
4480
|
function verificationRetryInstructions(verification) {
|
|
@@ -4564,7 +4482,7 @@ function verificationRetryInstructions(verification) {
|
|
|
4564
4482
|
`Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
|
|
4565
4483
|
];
|
|
4566
4484
|
}
|
|
4567
|
-
async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES
|
|
4485
|
+
async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
4568
4486
|
const repoRoot = process.cwd();
|
|
4569
4487
|
const scan = ctx.getStepOutput("project-scan");
|
|
4570
4488
|
const entities = ctx.getStepOutput(
|
|
@@ -4580,6 +4498,21 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4580
4498
|
languages: ctx.getStepOutput("confirm-language")?.languages ?? scan.languages,
|
|
4581
4499
|
frameworks: ctx.getStepOutput("confirm-framework")?.frameworks ?? scan.frameworks
|
|
4582
4500
|
};
|
|
4501
|
+
const normalizeFrameworkName = (name) => name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
4502
|
+
const confirmedPrimaryFramework = language.frameworks[0]?.name;
|
|
4503
|
+
const frameworkWasCorrected = confirmedPrimaryFramework !== void 0 && !scan.frameworks.some(
|
|
4504
|
+
(fw) => normalizeFrameworkName(fw.name) === normalizeFrameworkName(confirmedPrimaryFramework)
|
|
4505
|
+
);
|
|
4506
|
+
const publicEnvVarPrefixPromise = frameworkWasCorrected ? resolveEnvVarPrefix(confirmedPrimaryFramework).then(
|
|
4507
|
+
(r) => r.publicEnvVarPrefix,
|
|
4508
|
+
(err) => {
|
|
4509
|
+
logger.warn(
|
|
4510
|
+
{ err, framework: confirmedPrimaryFramework },
|
|
4511
|
+
"implement: could not re-resolve publicEnvVarPrefix after a framework correction; using the stale scan value"
|
|
4512
|
+
);
|
|
4513
|
+
return scan.publicEnvVarPrefix;
|
|
4514
|
+
}
|
|
4515
|
+
) : Promise.resolve(scan.publicEnvVarPrefix);
|
|
4583
4516
|
const selected = ctx.getStepOutput(
|
|
4584
4517
|
"select-index"
|
|
4585
4518
|
);
|
|
@@ -4630,9 +4563,6 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4630
4563
|
const targetIndex = selected?.selection;
|
|
4631
4564
|
useWizard.getState().setTargetIndex(targetIndex ?? null);
|
|
4632
4565
|
await assertGitRepoWithHead(repoRoot);
|
|
4633
|
-
if (await isWorkingTreeDirty(repoRoot)) {
|
|
4634
|
-
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
4635
|
-
}
|
|
4636
4566
|
const normalized = normalizeFindingPaths(findings);
|
|
4637
4567
|
const confirmed2 = normalized.confirmedEntities;
|
|
4638
4568
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
@@ -4644,314 +4574,303 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4644
4574
|
if (useCases.includes("ingestion")) {
|
|
4645
4575
|
ingestAppId = appId ?? (await requireApplication()).id;
|
|
4646
4576
|
}
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4577
|
+
let uploadFilePath;
|
|
4578
|
+
let uploadWarning;
|
|
4579
|
+
if (ingestionSource === "fileUpload") {
|
|
4580
|
+
const copied = await copyUploadIntoProject(
|
|
4581
|
+
repoRoot,
|
|
4582
|
+
INGEST_DIR,
|
|
4583
|
+
uploadSourcePath ?? ""
|
|
4584
|
+
);
|
|
4585
|
+
if (copied.ok) {
|
|
4586
|
+
uploadFilePath = copied.relPath;
|
|
4587
|
+
} else {
|
|
4588
|
+
ingestionSource = "generated";
|
|
4589
|
+
uploadWarning = `\u26A0\uFE0F Could not use the uploaded file (${copied.reason}); generating sample records instead.`;
|
|
4590
|
+
logger.warn(
|
|
4591
|
+
{ reason: copied.reason },
|
|
4592
|
+
"implement: file upload unavailable; falling back to generated sample records"
|
|
4658
4593
|
);
|
|
4659
|
-
if (copied.ok) {
|
|
4660
|
-
uploadFilePath = copied.relPath;
|
|
4661
|
-
} else {
|
|
4662
|
-
ingestionSource = "generated";
|
|
4663
|
-
uploadWarning = `\u26A0\uFE0F Could not use the uploaded file (${copied.reason}); generating sample records instead.`;
|
|
4664
|
-
logger.warn(
|
|
4665
|
-
{ reason: copied.reason },
|
|
4666
|
-
"implement: file upload unavailable; falling back to generated sample records"
|
|
4667
|
-
);
|
|
4668
|
-
}
|
|
4669
|
-
}
|
|
4670
|
-
const input = {
|
|
4671
|
-
findings: normalized,
|
|
4672
|
-
confirmed: confirmed2,
|
|
4673
|
-
searchLocation,
|
|
4674
|
-
targetIndex,
|
|
4675
|
-
language,
|
|
4676
|
-
appId,
|
|
4677
|
-
searchEnvVars: searchEnvVars(language, targetIndex, appId),
|
|
4678
|
-
ingestDir: INGEST_DIR,
|
|
4679
|
-
ingestionSource,
|
|
4680
|
-
uploadFilePath,
|
|
4681
|
-
searchUiTarget: searchUiTarget(language)
|
|
4682
|
-
};
|
|
4683
|
-
const summaries = [];
|
|
4684
|
-
if (uploadWarning) summaries.push(uploadWarning);
|
|
4685
|
-
let envSearchKey;
|
|
4686
|
-
let envAppIdMismatch = false;
|
|
4687
|
-
if (useCases.includes("search") && appId) {
|
|
4688
|
-
const envAppId = await readEnvVar(worktree, appIdVar(language));
|
|
4689
|
-
if (envAppId === appId) {
|
|
4690
|
-
envSearchKey = await readEnvVar(worktree, searchKeyVar(language));
|
|
4691
|
-
} else if (envAppId) {
|
|
4692
|
-
envAppIdMismatch = true;
|
|
4693
|
-
summaries.push(
|
|
4694
|
-
`\u26A0\uFE0F .env already sets ${appIdVar(language)}=${envAppId}, but the active Algolia application is ${appId}. The wizard left those values alone \u2014 update ${appIdVar(language)} and ${searchKeyVar(language)} by hand, or searches will fail.`
|
|
4695
|
-
);
|
|
4696
|
-
logger.warn(
|
|
4697
|
-
{ envAppId, appId },
|
|
4698
|
-
"implement: .env holds credentials for a different Algolia application; not reusing its search key"
|
|
4699
|
-
);
|
|
4700
|
-
}
|
|
4701
4594
|
}
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4595
|
+
}
|
|
4596
|
+
const publicEnvVarPrefix = await publicEnvVarPrefixPromise;
|
|
4597
|
+
const input = {
|
|
4598
|
+
findings: normalized,
|
|
4599
|
+
confirmed: confirmed2,
|
|
4600
|
+
searchLocation,
|
|
4601
|
+
targetIndex,
|
|
4602
|
+
language,
|
|
4603
|
+
publicEnvVarPrefix,
|
|
4604
|
+
appId,
|
|
4605
|
+
searchEnvVars: publicSearchEnvVars(publicEnvVarPrefix, targetIndex, appId),
|
|
4606
|
+
ingestDir: INGEST_DIR,
|
|
4607
|
+
ingestionSource,
|
|
4608
|
+
uploadFilePath,
|
|
4609
|
+
searchUiTarget: searchUiTarget(language)
|
|
4610
|
+
};
|
|
4611
|
+
const summaries = [];
|
|
4612
|
+
if (uploadWarning) summaries.push(uploadWarning);
|
|
4613
|
+
let envSearchKey;
|
|
4614
|
+
let envAppIdMismatch = false;
|
|
4615
|
+
if (useCases.includes("search") && appId) {
|
|
4616
|
+
const envAppId = await readEnvVar(
|
|
4617
|
+
repoRoot,
|
|
4618
|
+
publicAppIdVar(publicEnvVarPrefix)
|
|
4619
|
+
);
|
|
4620
|
+
if (envAppId === appId) {
|
|
4621
|
+
envSearchKey = await readEnvVar(
|
|
4622
|
+
repoRoot,
|
|
4623
|
+
publicSearchKeyVar(publicEnvVarPrefix)
|
|
4624
|
+
);
|
|
4625
|
+
} else if (envAppId) {
|
|
4626
|
+
envAppIdMismatch = true;
|
|
4627
|
+
const appIdVarName = publicAppIdVar(publicEnvVarPrefix);
|
|
4628
|
+
const searchKeyVarName = publicSearchKeyVar(publicEnvVarPrefix);
|
|
4629
|
+
summaries.push(
|
|
4630
|
+
`\u26A0\uFE0F .env already sets ${appIdVarName}=${envAppId}, but the active Algolia application is ${appId}. The wizard left those values alone \u2014 update ${appIdVarName} and ${searchKeyVarName} by hand, or searches will fail.`
|
|
4631
|
+
);
|
|
4632
|
+
logger.warn(
|
|
4633
|
+
{ envAppId, appId },
|
|
4634
|
+
"implement: .env holds credentials for a different Algolia application; not reusing its search key"
|
|
4635
|
+
);
|
|
4729
4636
|
}
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4637
|
+
}
|
|
4638
|
+
let finalSearchEnvVars = input.searchEnvVars;
|
|
4639
|
+
let agentRuns = 0;
|
|
4640
|
+
let ingestCommand;
|
|
4641
|
+
let ingestScriptRan = false;
|
|
4642
|
+
let ingestRecordCount;
|
|
4643
|
+
let ingestDurationMs;
|
|
4644
|
+
let ingestOutcomeMessage;
|
|
4645
|
+
const ingestKeyAppId = ingestAppId;
|
|
4646
|
+
const ingestionTools = ingestKeyAppId ? makeToolContext(repoRoot, async () => ({
|
|
4647
|
+
[APP_ID_VAR]: ingestKeyAppId,
|
|
4648
|
+
[API_KEY_VAR]: (await resolveWriteKey(targetIndex, ingestKeyAppId)).key,
|
|
4649
|
+
[INDEX_NAME_VAR]: targetIndex
|
|
4650
|
+
})) : void 0;
|
|
4651
|
+
const searchTools = makeToolContext(repoRoot);
|
|
4652
|
+
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
4653
|
+
if (agentRuns > 0) ctx.recordStepExecution();
|
|
4654
|
+
agentRuns += 1;
|
|
4655
|
+
return runAgent({
|
|
4656
|
+
instructions: buildAgentInstructions(
|
|
4657
|
+
currentUseCase,
|
|
4658
|
+
input,
|
|
4659
|
+
extraInstructions
|
|
4660
|
+
),
|
|
4661
|
+
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
4662
|
+
outputSchema: implementationOutputSchema,
|
|
4663
|
+
toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
|
|
4664
|
+
});
|
|
4665
|
+
}
|
|
4666
|
+
async function runVerificationUseCase() {
|
|
4667
|
+
if (agentRuns > 0) ctx.recordStepExecution();
|
|
4668
|
+
agentRuns += 1;
|
|
4669
|
+
return runAgent({
|
|
4670
|
+
instructions: buildAgentInstructions("verification", input),
|
|
4671
|
+
tools: toolsForUseCase("verification"),
|
|
4672
|
+
outputSchema: verificationOutputSchema,
|
|
4673
|
+
toolContext: searchTools
|
|
4674
|
+
});
|
|
4675
|
+
}
|
|
4676
|
+
if (useCases.includes("ingestion")) {
|
|
4677
|
+
let ingestFailureDetail;
|
|
4678
|
+
const result = await runImplementationUseCase("ingestion");
|
|
4679
|
+
summaries.push(formatSummary("ingestion", result.summary));
|
|
4680
|
+
ingestCommand = result.ingestCommand;
|
|
4681
|
+
const ingestionContext = ingestionTools ?? searchTools;
|
|
4682
|
+
const executions = ingestionContext.shell.executions;
|
|
4683
|
+
const {
|
|
4684
|
+
run: ingestRun,
|
|
4685
|
+
attempt: ingestAttempt,
|
|
4686
|
+
recordCount
|
|
4687
|
+
} = ingestOutcome(executions, ingestCommand);
|
|
4688
|
+
ingestScriptRan = ingestRun != null;
|
|
4689
|
+
ingestRecordCount = recordCount;
|
|
4690
|
+
ingestDurationMs = ingestRun?.durationMs;
|
|
4691
|
+
if (ingestScriptRan && ingestionContext.reviewed.length === 0) {
|
|
4692
|
+
summaries.push(
|
|
4693
|
+
"\u26A0\uFE0F The ingestion script ran without being shown to you for review. Read it in your project before trusting the index contents."
|
|
4694
|
+
);
|
|
4695
|
+
logger.warn(
|
|
4696
|
+
{ ingestCommand },
|
|
4697
|
+
"implement: ingestion ran without a reviewScript call"
|
|
4698
|
+
);
|
|
4739
4699
|
}
|
|
4740
|
-
if (
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
run: ingestRun,
|
|
4749
|
-
attempt: ingestAttempt,
|
|
4750
|
-
recordCount
|
|
4751
|
-
} = ingestOutcome(executions, ingestCommand);
|
|
4752
|
-
ingestScriptRan = ingestRun != null;
|
|
4753
|
-
ingestRecordCount = recordCount;
|
|
4754
|
-
ingestDurationMs = ingestRun?.durationMs;
|
|
4755
|
-
if (ingestScriptRan && ingestionContext.reviewed.length === 0) {
|
|
4756
|
-
summaries.push(
|
|
4757
|
-
"\u26A0\uFE0F The ingestion script ran without being shown to you for review. Read it in the worktree before trusting the index contents."
|
|
4758
|
-
);
|
|
4759
|
-
logger.warn(
|
|
4760
|
-
{ ingestCommand },
|
|
4761
|
-
"implement: ingestion ran without a reviewScript call"
|
|
4762
|
-
);
|
|
4700
|
+
if (ingestScriptRan) {
|
|
4701
|
+
ingestOutcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
|
|
4702
|
+
if (ingestRecordCount != null) {
|
|
4703
|
+
track("AI Wizard Ingest Successful", {
|
|
4704
|
+
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
4705
|
+
record_count: ingestRecordCount,
|
|
4706
|
+
duration_ms: ingestDurationMs ?? 0
|
|
4707
|
+
});
|
|
4763
4708
|
}
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
duration_ms: ingestDurationMs ?? 0
|
|
4771
|
-
});
|
|
4772
|
-
}
|
|
4773
|
-
} else {
|
|
4774
|
-
const { reason, detail } = ingestFailure(ingestAttempt, executions);
|
|
4775
|
-
ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
|
|
4776
|
-
ingestFailureDetail = detail;
|
|
4777
|
-
summaries.push(
|
|
4778
|
-
`\u26A0\uFE0F Ingestion did not complete: ${reason}.${detail ? `
|
|
4709
|
+
} else {
|
|
4710
|
+
const { reason, detail } = ingestFailure(ingestAttempt, executions);
|
|
4711
|
+
ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
|
|
4712
|
+
ingestFailureDetail = detail;
|
|
4713
|
+
summaries.push(
|
|
4714
|
+
`\u26A0\uFE0F Ingestion did not complete: ${reason}.${detail ? `
|
|
4779
4715
|
${detail}` : ""}`
|
|
4780
|
-
|
|
4781
|
-
|
|
4716
|
+
);
|
|
4717
|
+
logger.warn(
|
|
4718
|
+
{
|
|
4719
|
+
ingestCommand,
|
|
4720
|
+
reason,
|
|
4721
|
+
approved: ingestAttempt?.approved,
|
|
4722
|
+
exitCode: ingestAttempt?.exitCode,
|
|
4723
|
+
timedOut: ingestAttempt?.timedOut,
|
|
4724
|
+
commandsRun: executions.length
|
|
4725
|
+
},
|
|
4726
|
+
"implement: ingestion script did not complete successfully"
|
|
4727
|
+
);
|
|
4728
|
+
track("Error", {
|
|
4729
|
+
step: "Push Data",
|
|
4730
|
+
error: `ingestion did not complete: ${reason}`,
|
|
4731
|
+
product_area: "AI Wizard"
|
|
4732
|
+
});
|
|
4733
|
+
}
|
|
4734
|
+
const commandMessages = ingestCommand ? [`Ingestion command: ${ingestCommand}`] : [];
|
|
4735
|
+
await ctx.requestUserInput({
|
|
4736
|
+
prompt: "",
|
|
4737
|
+
promptType: "enterToContinue",
|
|
4738
|
+
options: [],
|
|
4739
|
+
// One message per line: Notices.tsx counts a message as one wrapped
|
|
4740
|
+
// line, so an embedded newline overflows the panel's height accounting.
|
|
4741
|
+
messages: [
|
|
4742
|
+
ingestOutcomeMessage,
|
|
4743
|
+
...ingestFailureDetail?.split("\n").filter((l) => l.trim()) ?? [],
|
|
4744
|
+
...commandMessages
|
|
4745
|
+
]
|
|
4746
|
+
});
|
|
4747
|
+
}
|
|
4748
|
+
if (useCases.includes("search")) {
|
|
4749
|
+
let extraInstructions = [];
|
|
4750
|
+
useWizard.getState().clearWrittenFiles();
|
|
4751
|
+
for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
|
|
4752
|
+
if (attempt > 1) {
|
|
4753
|
+
logger.info(
|
|
4782
4754
|
{
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
exitCode: ingestAttempt?.exitCode,
|
|
4787
|
-
timedOut: ingestAttempt?.timedOut,
|
|
4788
|
-
commandsRun: executions.length
|
|
4755
|
+
attempt,
|
|
4756
|
+
maxAttempts: MAX_IMPLEMENT_VERIFICATION_ATTEMPTS,
|
|
4757
|
+
extraInstructions
|
|
4789
4758
|
},
|
|
4790
|
-
"implement:
|
|
4759
|
+
"implement: retrying search implementation after failed verification"
|
|
4791
4760
|
);
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4761
|
+
}
|
|
4762
|
+
const { summary } = await runImplementationUseCase(
|
|
4763
|
+
"search",
|
|
4764
|
+
extraInstructions
|
|
4765
|
+
);
|
|
4766
|
+
summaries.push(formatSummary("search", summary));
|
|
4767
|
+
const verification = await runVerificationUseCase();
|
|
4768
|
+
summaries.push(formatSummary("verification", verification.summary));
|
|
4769
|
+
if (verification.sufficient) {
|
|
4770
|
+
ctx.setUserInput("implementation", "success");
|
|
4771
|
+
const searchFilesChanged = [
|
|
4772
|
+
...new Set(useWizard.getState().writtenFiles)
|
|
4773
|
+
].map((file) => relative6(repoRoot, file));
|
|
4774
|
+
track("AI Wizard Frontend Component Generated", {
|
|
4775
|
+
filePaths: searchFilesChanged
|
|
4776
|
+
});
|
|
4777
|
+
track("AI Wizard Wired to UI", {
|
|
4778
|
+
location_heuristic: searchLocation ?? "unknown"
|
|
4796
4779
|
});
|
|
4780
|
+
break;
|
|
4797
4781
|
}
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4782
|
+
if (attempt === MAX_IMPLEMENT_VERIFICATION_ATTEMPTS) {
|
|
4783
|
+
ctx.setUserInput("implementation", "fail");
|
|
4784
|
+
throw new Error(
|
|
4785
|
+
`Implementation verification failed after ${MAX_IMPLEMENT_VERIFICATION_ATTEMPTS} attempts: ${verification.additionalInstructions ?? verification.summary}`
|
|
4786
|
+
);
|
|
4801
4787
|
}
|
|
4802
|
-
|
|
4803
|
-
prompt: "",
|
|
4804
|
-
promptType: "enterToContinue",
|
|
4805
|
-
options: [],
|
|
4806
|
-
// The wizard never streams command output, so a failed run's tail is the
|
|
4807
|
-
// only place the developer sees why it failed. One message per line:
|
|
4808
|
-
// the panel's height accounting counts a message as one wrapped line
|
|
4809
|
-
// (see Notices.tsx), so an embedded newline overflows it.
|
|
4810
|
-
messages: [
|
|
4811
|
-
ingestOutcomeMessage,
|
|
4812
|
-
...ingestFailureDetail?.split("\n").filter((l) => l.trim()) ?? [],
|
|
4813
|
-
...commandMessages
|
|
4814
|
-
]
|
|
4815
|
-
});
|
|
4788
|
+
extraInstructions = verificationRetryInstructions(verification);
|
|
4816
4789
|
}
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4790
|
+
let searchKey;
|
|
4791
|
+
let searchKeyError;
|
|
4792
|
+
if (appId) {
|
|
4793
|
+
try {
|
|
4794
|
+
const resolved2 = await resolveSearchOnlyKey(
|
|
4795
|
+
targetIndex,
|
|
4796
|
+
appId,
|
|
4797
|
+
envSearchKey
|
|
4798
|
+
);
|
|
4799
|
+
searchKey = resolved2.key;
|
|
4800
|
+
summaries.push(
|
|
4801
|
+
resolved2.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
|
|
4802
|
+
);
|
|
4803
|
+
} catch (err) {
|
|
4804
|
+
searchKeyError = err.message;
|
|
4805
|
+
logger.warn(
|
|
4806
|
+
{ err: searchKeyError },
|
|
4807
|
+
"implement: could not provision a search-only API key; the .env value stays a placeholder"
|
|
4834
4808
|
);
|
|
4835
|
-
summaries.push(formatSummary("search", summary));
|
|
4836
|
-
const verification = await runVerificationUseCase();
|
|
4837
|
-
summaries.push(formatSummary("verification", verification.summary));
|
|
4838
|
-
if (verification.sufficient) {
|
|
4839
|
-
ctx.setUserInput("implementation", "success");
|
|
4840
|
-
const searchFilesChanged = (await listChangedFiles(worktree)).filter(
|
|
4841
|
-
(file) => !preSearchFiles.has(file)
|
|
4842
|
-
);
|
|
4843
|
-
track("AI Wizard Frontend Component Generated", {
|
|
4844
|
-
filePaths: searchFilesChanged
|
|
4845
|
-
});
|
|
4846
|
-
track("AI Wizard Wired to UI", {
|
|
4847
|
-
location_heuristic: searchLocation ?? "unknown"
|
|
4848
|
-
});
|
|
4849
|
-
break;
|
|
4850
|
-
}
|
|
4851
|
-
if (attempt === MAX_IMPLEMENT_VERIFICATION_ATTEMPTS) {
|
|
4852
|
-
ctx.setUserInput("implementation", "fail");
|
|
4853
|
-
throw new Error(
|
|
4854
|
-
`Implementation verification failed after ${MAX_IMPLEMENT_VERIFICATION_ATTEMPTS} attempts: ${verification.additionalInstructions ?? verification.summary}`
|
|
4855
|
-
);
|
|
4856
|
-
}
|
|
4857
|
-
extraInstructions = verificationRetryInstructions(verification);
|
|
4858
|
-
}
|
|
4859
|
-
let searchKey;
|
|
4860
|
-
let searchKeyError;
|
|
4861
|
-
if (appId) {
|
|
4862
|
-
try {
|
|
4863
|
-
const resolved2 = await resolveSearchOnlyKey(
|
|
4864
|
-
targetIndex,
|
|
4865
|
-
appId,
|
|
4866
|
-
envSearchKey
|
|
4867
|
-
);
|
|
4868
|
-
searchKey = resolved2.key;
|
|
4869
|
-
summaries.push(
|
|
4870
|
-
resolved2.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
|
|
4871
|
-
);
|
|
4872
|
-
} catch (err) {
|
|
4873
|
-
searchKeyError = err.message;
|
|
4874
|
-
logger.warn(
|
|
4875
|
-
{ err: searchKeyError },
|
|
4876
|
-
"implement: could not provision a search-only API key; the .env value stays a placeholder"
|
|
4877
|
-
);
|
|
4878
|
-
}
|
|
4879
4809
|
}
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4810
|
+
}
|
|
4811
|
+
finalSearchEnvVars = publicSearchEnvVars(
|
|
4812
|
+
publicEnvVarPrefix,
|
|
4813
|
+
targetIndex,
|
|
4814
|
+
appId,
|
|
4815
|
+
searchKey
|
|
4816
|
+
);
|
|
4817
|
+
const resolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4818
|
+
(v) => !v.value.startsWith("<")
|
|
4819
|
+
);
|
|
4820
|
+
if (resolvedSearchEnvVars.length > 0) {
|
|
4821
|
+
const written = await writeSearchEnvValues(
|
|
4822
|
+
repoRoot,
|
|
4823
|
+
resolvedSearchEnvVars
|
|
4888
4824
|
);
|
|
4889
|
-
if (
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4825
|
+
if (written.length > 0) {
|
|
4826
|
+
summaries.push(`Wrote ${written.join(", ")} to .env.`);
|
|
4827
|
+
}
|
|
4828
|
+
const ignored = await ensureGitIgnored(repoRoot, join12(repoRoot, ".env"));
|
|
4829
|
+
if (ignored === "added") {
|
|
4830
|
+
summaries.push("Added .env to .gitignore.");
|
|
4831
|
+
} else if (ignored === "tracked") {
|
|
4832
|
+
summaries.push(
|
|
4833
|
+
'\u26A0\uFE0F .env is tracked by git, so a .gitignore rule cannot un-stage it. Run "git rm --cached .env" before committing, or the credentials go into history.'
|
|
4893
4834
|
);
|
|
4894
|
-
if (written.length > 0) {
|
|
4895
|
-
summaries.push(`Wrote ${written.join(", ")} to .env.`);
|
|
4896
|
-
}
|
|
4897
|
-
const ignored = await ensureGitIgnored(worktree, join12(worktree, ".env"));
|
|
4898
|
-
if (ignored === "added") {
|
|
4899
|
-
summaries.push("Added .env to .gitignore.");
|
|
4900
|
-
} else if (ignored === "tracked") {
|
|
4901
|
-
summaries.push(
|
|
4902
|
-
'\u26A0\uFE0F .env is tracked by git, so a .gitignore rule cannot un-stage it. Run "git rm --cached .env" before committing, or the credentials go into history.'
|
|
4903
|
-
);
|
|
4904
|
-
}
|
|
4905
|
-
const stale = [];
|
|
4906
|
-
for (const v of resolvedSearchEnvVars) {
|
|
4907
|
-
if (written.includes(v.name)) continue;
|
|
4908
|
-
const current = await readEnvVar(worktree, v.name);
|
|
4909
|
-
if (current && current !== v.value) stale.push(v);
|
|
4910
|
-
}
|
|
4911
|
-
if (stale.length > 0 && !envAppIdMismatch) {
|
|
4912
|
-
summaries.push(
|
|
4913
|
-
`\u26A0\uFE0F .env already assigns a different value to ${stale.map((v) => `${v.name} (should be ${v.value})`).join(", ")} \u2014 the wizard left it alone. Fix it by hand, or searches will fail.`
|
|
4914
|
-
);
|
|
4915
|
-
logger.warn(
|
|
4916
|
-
{ vars: stale.map((v) => v.name) },
|
|
4917
|
-
"implement: .env holds different values for the resolved search credentials; not overwriting them"
|
|
4918
|
-
);
|
|
4919
|
-
}
|
|
4920
4835
|
}
|
|
4921
|
-
const
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4836
|
+
const stale = [];
|
|
4837
|
+
for (const v of resolvedSearchEnvVars) {
|
|
4838
|
+
if (written.includes(v.name)) continue;
|
|
4839
|
+
const current = await readEnvVar(repoRoot, v.name);
|
|
4840
|
+
if (current && current !== v.value) stale.push(v);
|
|
4841
|
+
}
|
|
4842
|
+
if (stale.length > 0 && !envAppIdMismatch) {
|
|
4925
4843
|
summaries.push(
|
|
4926
|
-
|
|
4927
|
-
|
|
4844
|
+
`\u26A0\uFE0F .env already assigns a different value to ${stale.map((v) => `${v.name} (should be ${v.value})`).join(", ")} \u2014 the wizard left it alone. Fix it by hand, or searches will fail.`
|
|
4845
|
+
);
|
|
4846
|
+
logger.warn(
|
|
4847
|
+
{ vars: stale.map((v) => v.name) },
|
|
4848
|
+
"implement: .env holds different values for the resolved search credentials; not overwriting them"
|
|
4928
4849
|
);
|
|
4929
4850
|
}
|
|
4930
|
-
} else {
|
|
4931
|
-
ctx.setUserInput("implementation", "success");
|
|
4932
4851
|
}
|
|
4933
|
-
const
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4852
|
+
const unresolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4853
|
+
(v) => v.value.startsWith("<")
|
|
4854
|
+
);
|
|
4855
|
+
if (unresolvedSearchEnvVars.length > 0) {
|
|
4856
|
+
summaries.push(
|
|
4857
|
+
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.` + (searchKeyError ? ` Reason: ${searchKeyError}` : "")
|
|
4937
4858
|
);
|
|
4938
4859
|
}
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
filesChanged,
|
|
4942
|
-
summary: summaries.join("\n\n"),
|
|
4943
|
-
worktreePath: worktree,
|
|
4944
|
-
...useCases.includes("ingestion") && ingestCommand ? {
|
|
4945
|
-
ingestCommand,
|
|
4946
|
-
ingestScriptRan,
|
|
4947
|
-
...ingestRecordCount != null ? { ingestRecordCount } : {},
|
|
4948
|
-
...ingestDurationMs != null ? { ingestDurationMs } : {}
|
|
4949
|
-
} : {},
|
|
4950
|
-
...useCases.includes("search") ? { searchEnvVars: finalSearchEnvVars } : {}
|
|
4951
|
-
};
|
|
4952
|
-
} finally {
|
|
4953
|
-
process.chdir(repoRoot);
|
|
4860
|
+
} else {
|
|
4861
|
+
ctx.setUserInput("implementation", "success");
|
|
4954
4862
|
}
|
|
4863
|
+
return {
|
|
4864
|
+
ingestionSource,
|
|
4865
|
+
summary: summaries.join("\n\n"),
|
|
4866
|
+
...useCases.includes("ingestion") && ingestCommand ? {
|
|
4867
|
+
ingestCommand,
|
|
4868
|
+
ingestScriptRan,
|
|
4869
|
+
...ingestRecordCount != null ? { ingestRecordCount } : {},
|
|
4870
|
+
...ingestDurationMs != null ? { ingestDurationMs } : {}
|
|
4871
|
+
} : {},
|
|
4872
|
+
...useCases.includes("search") ? { searchEnvVars: finalSearchEnvVars } : {}
|
|
4873
|
+
};
|
|
4955
4874
|
}
|
|
4956
4875
|
|
|
4957
4876
|
// src/workflows/default.ts
|
|
@@ -4990,8 +4909,8 @@ var defaultWorkflow = {
|
|
|
4990
4909
|
defineStep({
|
|
4991
4910
|
id: "select-index",
|
|
4992
4911
|
title: "Set up index",
|
|
4993
|
-
outputSchema:
|
|
4994
|
-
selection:
|
|
4912
|
+
outputSchema: z30.object({
|
|
4913
|
+
selection: z30.string()
|
|
4995
4914
|
}),
|
|
4996
4915
|
run: (ctx) => selectIndexStep(ctx)
|
|
4997
4916
|
}),
|
|
@@ -5023,10 +4942,7 @@ var defaultWorkflow = {
|
|
|
5023
4942
|
ctx.notify({
|
|
5024
4943
|
messages: ["Building your Algolia search experience\u2026"]
|
|
5025
4944
|
});
|
|
5026
|
-
|
|
5027
|
-
"ingestion"
|
|
5028
|
-
);
|
|
5029
|
-
return implement(ctx, ["search"], ingestion2?.worktreePath);
|
|
4945
|
+
return implement(ctx, ["search"]);
|
|
5030
4946
|
}
|
|
5031
4947
|
}),
|
|
5032
4948
|
defineStep({
|
|
@@ -5041,9 +4957,8 @@ var defaultWorkflow = {
|
|
|
5041
4957
|
"ingestion"
|
|
5042
4958
|
);
|
|
5043
4959
|
return reviewStep(ctx, {
|
|
5044
|
-
//
|
|
5045
|
-
//
|
|
5046
|
-
// an LLM-paraphrased command risks being wrong.
|
|
4960
|
+
// ingestCommand was already shown verbatim as a notice; an
|
|
4961
|
+
// LLM-paraphrased restatement in nextSteps risks being wrong.
|
|
5047
4962
|
nextStepsGuidance: ingestion2?.ingestScriptRan ? "The wizard already ran the ingestion script and records are in the index. Do NOT tell the user to run it again; instead point them at the target index to confirm the records. Do not restate the ingestion command \u2014 the wizard already showed it to them." : "Tell the user to run the ingestion script; do not restate the exact command \u2014 the wizard already showed it to them above."
|
|
5048
4963
|
});
|
|
5049
4964
|
}
|
|
@@ -5063,6 +4978,7 @@ function getWorkflow(id) {
|
|
|
5063
4978
|
var projectScan2 = {
|
|
5064
4979
|
languages: [{ name: "TypeScript", version: "5.7.2" }],
|
|
5065
4980
|
frameworks: [{ name: "Next.js", version: "15.1.0" }],
|
|
4981
|
+
publicEnvVarPrefix: "NEXT_PUBLIC_",
|
|
5066
4982
|
ingestionAnalysis: [
|
|
5067
4983
|
{
|
|
5068
4984
|
name: "Product",
|
|
@@ -5089,7 +5005,6 @@ var selectIndex = {
|
|
|
5089
5005
|
selection: "wizard_seed_products"
|
|
5090
5006
|
};
|
|
5091
5007
|
var ingestion = {
|
|
5092
|
-
filesChanged: ["algolia/ingest.mjs", "algolia/records.json", "package.json"],
|
|
5093
5008
|
summary: "Generated sample Product records and an ingestion script that pushes them to the target index with algoliasearch.",
|
|
5094
5009
|
ingestCommand: "node algolia/ingest.mjs",
|
|
5095
5010
|
ingestScriptRan: true,
|
|
@@ -5101,7 +5016,6 @@ var confirmFramework2 = {
|
|
|
5101
5016
|
frameworks: projectScan2.frameworks
|
|
5102
5017
|
};
|
|
5103
5018
|
var search = {
|
|
5104
|
-
filesChanged: ["src/components/Search.tsx", "src/components/Header.tsx", ".env"],
|
|
5105
5019
|
summary: "Added an InstantSearch-powered search box and results list, mounted in the shared header component.",
|
|
5106
5020
|
ingestionSource: "generated",
|
|
5107
5021
|
searchEnvVars: [
|
|
@@ -5114,7 +5028,7 @@ var review = {
|
|
|
5114
5028
|
"Ingested 25 generated Product records into wizard_seed_products.",
|
|
5115
5029
|
"Added an InstantSearch search experience to the shared header."
|
|
5116
5030
|
],
|
|
5117
|
-
reviewPrompt: "Review the Algolia ingestion and search changes
|
|
5031
|
+
reviewPrompt: "Review the Algolia ingestion and search changes.",
|
|
5118
5032
|
nextSteps: ["Point the ingestion script at your real product data."]
|
|
5119
5033
|
};
|
|
5120
5034
|
var SEEDS = {
|
|
@@ -5231,9 +5145,9 @@ Options:
|
|
|
5231
5145
|
steps pre-filled with test data. Pass with no value to print
|
|
5232
5146
|
the step ids. See CONTRIBUTING.md.
|
|
5233
5147
|
--no-telemetry Send no telemetry or analytics for this run.
|
|
5234
|
-
--reset-on-run Wipe this project's wizard state (run state, AI consent
|
|
5235
|
-
|
|
5236
|
-
|
|
5148
|
+
--reset-on-run Wipe this project's wizard state (run state, AI consent)
|
|
5149
|
+
before starting, so the run behaves like a first-ever
|
|
5150
|
+
run. Also drops every API key the wizard has
|
|
5237
5151
|
stored in your keychain (or, where the platform has none,
|
|
5238
5152
|
the encrypted file it falls back to \u2014 see CONTRIBUTING.md),
|
|
5239
5153
|
for this project and any other, so later runs create new
|
|
@@ -5273,7 +5187,7 @@ function parseCliArgs(argv) {
|
|
|
5273
5187
|
}
|
|
5274
5188
|
|
|
5275
5189
|
// src/lib/resetState.ts
|
|
5276
|
-
import { readdir as
|
|
5190
|
+
import { readdir as readdir3, rm as rm2 } from "node:fs/promises";
|
|
5277
5191
|
import { join as join13 } from "node:path";
|
|
5278
5192
|
var KEEP = ["wizard.log"];
|
|
5279
5193
|
async function resetProjectState() {
|
|
@@ -5281,7 +5195,7 @@ async function resetProjectState() {
|
|
|
5281
5195
|
await forgetResolvedKeys();
|
|
5282
5196
|
let entries;
|
|
5283
5197
|
try {
|
|
5284
|
-
entries = await
|
|
5198
|
+
entries = await readdir3(dir);
|
|
5285
5199
|
} catch {
|
|
5286
5200
|
return { dir, removed: [] };
|
|
5287
5201
|
}
|