@algolia/wizard 0.21.0-rc.111.195 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/main.js +461 -338
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,8 +12,22 @@ 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
|
|
15
18
|
```
|
|
16
19
|
|
|
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
|
+
|
|
17
31
|
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`.
|
|
18
32
|
|
|
19
33
|
You'll also be asked once to consent to AI-authored changes to the repository.
|
package/dist/main.js
CHANGED
|
@@ -251,7 +251,6 @@ var useWizard = create((set, get) => ({
|
|
|
251
251
|
_noticeTimer: null,
|
|
252
252
|
cliOutput: [],
|
|
253
253
|
targetIndex: null,
|
|
254
|
-
writtenFiles: [],
|
|
255
254
|
logs: [],
|
|
256
255
|
error: null,
|
|
257
256
|
inputReq: null,
|
|
@@ -345,8 +344,6 @@ var useWizard = create((set, get) => ({
|
|
|
345
344
|
})),
|
|
346
345
|
clearCliOutput: () => set({ cliOutput: [] }),
|
|
347
346
|
setTargetIndex: (index) => set({ targetIndex: index }),
|
|
348
|
-
recordWrittenFile: (path) => set((s) => ({ writtenFiles: [...s.writtenFiles, path] })),
|
|
349
|
-
clearWrittenFiles: () => set({ writtenFiles: [] }),
|
|
350
347
|
logStart: (kind, name, input) => {
|
|
351
348
|
const id = nanoid();
|
|
352
349
|
set((s) => ({
|
|
@@ -394,7 +391,6 @@ var useWizard = create((set, get) => ({
|
|
|
394
391
|
notices: [],
|
|
395
392
|
cliOutput: [],
|
|
396
393
|
targetIndex: null,
|
|
397
|
-
writtenFiles: [],
|
|
398
394
|
logs: [],
|
|
399
395
|
error: null,
|
|
400
396
|
inputReq: null,
|
|
@@ -504,7 +500,7 @@ function Table({ columns, rows }) {
|
|
|
504
500
|
}, [termCols, columns, rows]);
|
|
505
501
|
if (rows.length === 0) return null;
|
|
506
502
|
const lines = formatTable(columns, rows, width || void 0);
|
|
507
|
-
return /* @__PURE__ */ jsx(Box2, { flexDirection: "column", ref, children: lines.map((line, i) => /* @__PURE__ */ jsx(Text2, { wrap: "truncate", children: line }, `tbl-${i}`)) });
|
|
503
|
+
return /* @__PURE__ */ jsx(Box2, { flexDirection: "column", ref, children: lines.map((line, i) => /* @__PURE__ */ jsx(Text2, { color: COLORS.primary, wrap: "truncate", children: line }, `tbl-${i}`)) });
|
|
508
504
|
}
|
|
509
505
|
function formatTable(columns, rows, width) {
|
|
510
506
|
const natural = columns.map(
|
|
@@ -936,7 +932,7 @@ function SelectPrompt({
|
|
|
936
932
|
`row-${i}`
|
|
937
933
|
);
|
|
938
934
|
}) }),
|
|
939
|
-
/* @__PURE__ */ jsx5(Box7, { flexShrink: 0, children: /* @__PURE__ */ jsx5(Text7, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs6(Text7, { children: [
|
|
935
|
+
/* @__PURE__ */ jsx5(Box7, { flexShrink: 0, children: /* @__PURE__ */ jsx5(Text7, { color: COLORS.dim, children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs6(Text7, { color: COLORS.dim, children: [
|
|
940
936
|
i > 0 ? " " : "",
|
|
941
937
|
/* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: key }),
|
|
942
938
|
/* @__PURE__ */ jsxs6(Text7, { color: COLORS.dim, children: [
|
|
@@ -1204,7 +1200,7 @@ var accessItems = [
|
|
|
1204
1200
|
{
|
|
1205
1201
|
tag: "WRITE",
|
|
1206
1202
|
title: "Code changes",
|
|
1207
|
-
description: "creates & edits files (search UI, config)
|
|
1203
|
+
description: "creates & edits files (search UI, config) in a throwaway git worktree \u2014 your checkout is never touched."
|
|
1208
1204
|
},
|
|
1209
1205
|
{
|
|
1210
1206
|
tag: "EXEC",
|
|
@@ -1219,7 +1215,7 @@ var accessItems = [
|
|
|
1219
1215
|
{
|
|
1220
1216
|
tag: "KEY",
|
|
1221
1217
|
title: "Credentials",
|
|
1222
|
-
description: "writes your Algolia app id and a search-only key (safe to expose) to .env in
|
|
1218
|
+
description: "writes your Algolia app id and a search-only key (safe to expose) to .env in the worktree."
|
|
1223
1219
|
}
|
|
1224
1220
|
];
|
|
1225
1221
|
var neverItems = [
|
|
@@ -1250,7 +1246,7 @@ function NeverLine({
|
|
|
1250
1246
|
}) {
|
|
1251
1247
|
const used = segments.reduce((n, s) => n + s.text.length, 0);
|
|
1252
1248
|
const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
|
|
1253
|
-
return /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
1249
|
+
return /* @__PURE__ */ jsxs9(Text10, { color: COLORS.primary, children: [
|
|
1254
1250
|
/* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" }),
|
|
1255
1251
|
" ".repeat(NEVER_BOX_PAD_X),
|
|
1256
1252
|
segments.map((s, i) => /* @__PURE__ */ jsx8(Text10, { color: s.color, bold: s.bold, children: s.text }, i)),
|
|
@@ -1282,7 +1278,7 @@ function LearnMore() {
|
|
|
1282
1278
|
/* @__PURE__ */ jsx8(Text10, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
|
|
1283
1279
|
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, marginTop: 1, children: [
|
|
1284
1280
|
/* @__PURE__ */ jsx8(Box10, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx8(Text10, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
|
|
1285
|
-
/* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
1281
|
+
/* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text10, { color: COLORS.primary, children: [
|
|
1286
1282
|
/* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: item.title }),
|
|
1287
1283
|
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
|
|
1288
1284
|
] }) })
|
|
@@ -1402,9 +1398,9 @@ function Progress() {
|
|
|
1402
1398
|
const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
|
|
1403
1399
|
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
|
|
1404
1400
|
/* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "STEP" }),
|
|
1405
|
-
/* @__PURE__ */ jsx10(Text12, { bold: true, children: activeStepNumber }),
|
|
1406
|
-
/* @__PURE__ */ jsx10(Text12, { bold: true, children: "/" }),
|
|
1407
|
-
/* @__PURE__ */ jsx10(Text12, { bold: true, children: visibleSteps.length })
|
|
1401
|
+
/* @__PURE__ */ jsx10(Text12, { color: COLORS.strong, bold: true, children: activeStepNumber }),
|
|
1402
|
+
/* @__PURE__ */ jsx10(Text12, { color: COLORS.strong, bold: true, children: "/" }),
|
|
1403
|
+
/* @__PURE__ */ jsx10(Text12, { color: COLORS.strong, bold: true, children: visibleSteps.length })
|
|
1408
1404
|
] });
|
|
1409
1405
|
}
|
|
1410
1406
|
|
|
@@ -1802,7 +1798,6 @@ function App() {
|
|
|
1802
1798
|
/* @__PURE__ */ jsx14(CliOutput, {}),
|
|
1803
1799
|
/* @__PURE__ */ jsx14(Notices, {}),
|
|
1804
1800
|
/* @__PURE__ */ jsx14(PromptInput, {}),
|
|
1805
|
-
phase === "running" && showSidebar && /* @__PURE__ */ jsx14(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx14(CurrentStep, {}) }),
|
|
1806
1801
|
phase === "error" && error && /* @__PURE__ */ jsx14(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text16, { color: COLORS.status.error, children: [
|
|
1807
1802
|
"\u2716 ",
|
|
1808
1803
|
error
|
|
@@ -2500,7 +2495,6 @@ function writeFileTool(ctx) {
|
|
|
2500
2495
|
}
|
|
2501
2496
|
await mkdir3(dirname4(resolved2.target), { recursive: true });
|
|
2502
2497
|
await writeFile3(resolved2.target, content, "utf8");
|
|
2503
|
-
useWizard.getState().recordWrittenFile(resolved2.target);
|
|
2504
2498
|
return `Wrote to ${filePath}`;
|
|
2505
2499
|
} catch (err) {
|
|
2506
2500
|
return `Error writing ${filePath}: ${err.message}`;
|
|
@@ -3268,12 +3262,20 @@ var RECORD_MODEL = "claude-haiku-4-5";
|
|
|
3268
3262
|
var MAX_RECORDS = 100;
|
|
3269
3263
|
var BATCH_SIZE = 10;
|
|
3270
3264
|
var MAX_BATCH_ATTEMPTS = 3;
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3265
|
+
function defaultCreateModel() {
|
|
3266
|
+
const token = getAuthToken();
|
|
3267
|
+
if (!token) {
|
|
3268
|
+
throw new Error("Not authenticated: no user token available");
|
|
3269
|
+
}
|
|
3270
|
+
return createAnthropic({
|
|
3271
|
+
apiKey: token,
|
|
3272
|
+
baseURL: PROXY_BASE_URL,
|
|
3273
|
+
fetch: proxyFetch
|
|
3274
|
+
});
|
|
3275
|
+
}
|
|
3276
|
+
function generateRecordTool(ctx, createModel = defaultCreateModel) {
|
|
3275
3277
|
return tool10({
|
|
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.",
|
|
3278
|
+
description: "Generate realistic sample records for an entity and write them to a JSON file in the worktree. 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.",
|
|
3277
3279
|
inputSchema: z17.object({
|
|
3278
3280
|
entityName: z17.string().describe("Name of the entity to generate records for."),
|
|
3279
3281
|
attributes: z17.array(z17.string()).describe("Attribute names each record must contain."),
|
|
@@ -3283,6 +3285,7 @@ function generateRecordTool(ctx) {
|
|
|
3283
3285
|
execute: async ({ entityName, attributes, count, hint }) => {
|
|
3284
3286
|
logger.info({ entityName, count }, "called generateRecord tool");
|
|
3285
3287
|
try {
|
|
3288
|
+
const anthropic = createModel();
|
|
3286
3289
|
const value = z17.union([z17.string(), z17.number(), z17.boolean(), z17.null()]);
|
|
3287
3290
|
const recordSchema = z17.object(
|
|
3288
3291
|
Object.fromEntries(attributes.map((attr) => [attr, value]))
|
|
@@ -3301,10 +3304,6 @@ function generateRecordTool(ctx) {
|
|
|
3301
3304
|
prompt: [
|
|
3302
3305
|
`Generate ${batchCount} realistic, varied sample records for the "${entityName}" entity.`,
|
|
3303
3306
|
hint ? `Context: ${hint}` : "",
|
|
3304
|
-
// Batches run concurrently with identical prompts, so each call
|
|
3305
|
-
// would otherwise drift to the same high-probability values and
|
|
3306
|
-
// collide across batches. This per-batch seed pushes each call
|
|
3307
|
-
// into a different region of the output space.
|
|
3308
3307
|
`Variety seed: ${nanoid2()}. Use it to diversify values.`
|
|
3309
3308
|
].filter(Boolean).join("\n")
|
|
3310
3309
|
});
|
|
@@ -3337,8 +3336,15 @@ function generateRecordTool(ctx) {
|
|
|
3337
3336
|
return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
|
|
3338
3337
|
}
|
|
3339
3338
|
await mkdir5(dirname6(resolved2.target), { recursive: true });
|
|
3340
|
-
await writeFile6(
|
|
3341
|
-
|
|
3339
|
+
await writeFile6(
|
|
3340
|
+
resolved2.target,
|
|
3341
|
+
JSON.stringify(records, null, 2),
|
|
3342
|
+
"utf8"
|
|
3343
|
+
);
|
|
3344
|
+
logger.info(
|
|
3345
|
+
{ entityName, count: records.length, relPath },
|
|
3346
|
+
"generateRecord wrote records to disk"
|
|
3347
|
+
);
|
|
3342
3348
|
return {
|
|
3343
3349
|
filePath: relPath,
|
|
3344
3350
|
count: records.length,
|
|
@@ -3432,7 +3438,7 @@ async function runAgent(req) {
|
|
|
3432
3438
|
if (!token) {
|
|
3433
3439
|
throw new Error("Not authenticated: no user token available");
|
|
3434
3440
|
}
|
|
3435
|
-
const
|
|
3441
|
+
const anthropic = createAnthropic2({
|
|
3436
3442
|
apiKey: token,
|
|
3437
3443
|
baseURL: PROXY_BASE_URL,
|
|
3438
3444
|
fetch: proxyFetch
|
|
@@ -3448,7 +3454,7 @@ async function runAgent(req) {
|
|
|
3448
3454
|
"Call notifyUser whenever you start a new phase of work or your focus shifts (e.g. moving from reading code to writing files) \u2014 a short, plain-language, high-level update. Do not call it for every tool use."
|
|
3449
3455
|
];
|
|
3450
3456
|
const agent = new ToolLoopAgent({
|
|
3451
|
-
model:
|
|
3457
|
+
model: anthropic(MODEL_BY_SIZE[req.modelSize ?? "medium"]),
|
|
3452
3458
|
// Cache tools + system on the last system block. Tools render before
|
|
3453
3459
|
// system, so one breakpoint here caches both, reused on every loop turn
|
|
3454
3460
|
// after the first.
|
|
@@ -3635,7 +3641,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3635
3641
|
// package.json
|
|
3636
3642
|
var package_default = {
|
|
3637
3643
|
name: "@algolia/wizard",
|
|
3638
|
-
version: "0.
|
|
3644
|
+
version: "0.22.0",
|
|
3639
3645
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3640
3646
|
type: "module",
|
|
3641
3647
|
engines: {
|
|
@@ -4002,10 +4008,11 @@ ${JSON.stringify(s.output, null, 2)}`
|
|
|
4002
4008
|
function formatReviewSummary(result) {
|
|
4003
4009
|
const nextStepLines = result.nextSteps.map((step) => {
|
|
4004
4010
|
const isIngestCommand = step.includes(".algolia-wizard/ingest.sh");
|
|
4011
|
+
const isWorktreeCommand = step.includes("/worktrees/");
|
|
4005
4012
|
return {
|
|
4006
4013
|
text: `\u2192 ${step}`,
|
|
4007
|
-
color: isIngestCommand ? COLORS.brand : void 0,
|
|
4008
|
-
bold: isIngestCommand
|
|
4014
|
+
color: isIngestCommand ? COLORS.brand : isWorktreeCommand ? COLORS.secondary : void 0,
|
|
4015
|
+
bold: isIngestCommand || isWorktreeCommand
|
|
4009
4016
|
};
|
|
4010
4017
|
});
|
|
4011
4018
|
return [
|
|
@@ -4040,13 +4047,15 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
4040
4047
|
|
|
4041
4048
|
// src/actions/implement.ts
|
|
4042
4049
|
import z29 from "zod";
|
|
4043
|
-
import { join as join12
|
|
4050
|
+
import { join as join12 } from "node:path";
|
|
4044
4051
|
|
|
4045
|
-
// src/lib/
|
|
4052
|
+
// src/lib/worktree.ts
|
|
4046
4053
|
import { execFile as execFile2 } from "node:child_process";
|
|
4047
|
-
import { copyFile, mkdir as mkdir6, readFile as readFile8, stat as stat3, writeFile as writeFile7 } from "node:fs/promises";
|
|
4054
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile8, stat as stat3, writeFile as writeFile7 } from "node:fs/promises";
|
|
4048
4055
|
import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join10, resolve as resolve3 } from "node:path";
|
|
4049
4056
|
var MAX_BUFFER = 32 * 1024 * 1024;
|
|
4057
|
+
var MAX_WIZARD_WORKTREES = 3;
|
|
4058
|
+
var WIZARD_BRANCH_PREFIX = "wizard/implement-";
|
|
4050
4059
|
function git(args) {
|
|
4051
4060
|
return new Promise((resolve4, reject) => {
|
|
4052
4061
|
execFile2("git", args, { maxBuffer: MAX_BUFFER }, (err, stdout, stderr) => {
|
|
@@ -4069,7 +4078,44 @@ async function assertGitRepoWithHead(repoRoot) {
|
|
|
4069
4078
|
);
|
|
4070
4079
|
}
|
|
4071
4080
|
}
|
|
4072
|
-
async function
|
|
4081
|
+
async function isWorkingTreeDirty(repoRoot) {
|
|
4082
|
+
const out = await git(["-C", repoRoot, "status", "--porcelain"]);
|
|
4083
|
+
return out.trim().length > 0;
|
|
4084
|
+
}
|
|
4085
|
+
async function pruneOldWorktrees(repoRoot) {
|
|
4086
|
+
const dir = join10(stateDir(repoRoot), "worktrees");
|
|
4087
|
+
const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
4088
|
+
for (const slug of stale) {
|
|
4089
|
+
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
4090
|
+
try {
|
|
4091
|
+
await git([
|
|
4092
|
+
"-C",
|
|
4093
|
+
repoRoot,
|
|
4094
|
+
"worktree",
|
|
4095
|
+
"remove",
|
|
4096
|
+
"--force",
|
|
4097
|
+
join10(dir, slug)
|
|
4098
|
+
]);
|
|
4099
|
+
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
4100
|
+
} catch (err) {
|
|
4101
|
+
logger.warn(
|
|
4102
|
+
{ branch, err: err.message },
|
|
4103
|
+
"createWorktree: failed to prune a stale wizard worktree; continuing"
|
|
4104
|
+
);
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
async function createWorktree(repoRoot) {
|
|
4109
|
+
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
4110
|
+
const dirSlug = branch.replace(/\//g, "-");
|
|
4111
|
+
const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
|
|
4112
|
+
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
4113
|
+
await pruneOldWorktrees(repoRoot);
|
|
4114
|
+
await mkdir6(dirname7(path), { recursive: true });
|
|
4115
|
+
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
4116
|
+
return { path, branch };
|
|
4117
|
+
}
|
|
4118
|
+
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
4073
4119
|
const trimmed = sourcePath.trim();
|
|
4074
4120
|
if (!trimmed) {
|
|
4075
4121
|
return { ok: false, reason: "no file path was provided" };
|
|
@@ -4083,10 +4129,7 @@ async function copyUploadIntoProject(repoRoot, ingestDir, sourcePath) {
|
|
|
4083
4129
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
4084
4130
|
}
|
|
4085
4131
|
const relPath = join10(ingestDir, basename2(source));
|
|
4086
|
-
const dest = join10(
|
|
4087
|
-
if (resolve3(source) === resolve3(dest)) {
|
|
4088
|
-
return { ok: true, relPath };
|
|
4089
|
-
}
|
|
4132
|
+
const dest = join10(worktreePath, relPath);
|
|
4090
4133
|
try {
|
|
4091
4134
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
4092
4135
|
await copyFile(source, dest);
|
|
@@ -4101,10 +4144,10 @@ async function copyUploadIntoProject(repoRoot, ingestDir, sourcePath) {
|
|
|
4101
4144
|
function hasEnvVar(content, name) {
|
|
4102
4145
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
4103
4146
|
}
|
|
4104
|
-
async function readEnvVar(
|
|
4147
|
+
async function readEnvVar(worktreePath, name) {
|
|
4105
4148
|
let content;
|
|
4106
4149
|
try {
|
|
4107
|
-
content = await readFile8(join10(
|
|
4150
|
+
content = await readFile8(join10(worktreePath, ".env"), "utf8");
|
|
4108
4151
|
} catch (err) {
|
|
4109
4152
|
if (err.code !== "ENOENT") throw err;
|
|
4110
4153
|
return void 0;
|
|
@@ -4118,8 +4161,8 @@ async function readEnvVar(repoRoot, name) {
|
|
|
4118
4161
|
if (!value || value.startsWith("<")) return void 0;
|
|
4119
4162
|
return value;
|
|
4120
4163
|
}
|
|
4121
|
-
async function writeSearchEnvValues(
|
|
4122
|
-
const target = join10(
|
|
4164
|
+
async function writeSearchEnvValues(worktreePath, vars) {
|
|
4165
|
+
const target = join10(worktreePath, ".env");
|
|
4123
4166
|
let existing = "";
|
|
4124
4167
|
try {
|
|
4125
4168
|
existing = await readFile8(target, "utf8");
|
|
@@ -4134,27 +4177,61 @@ async function writeSearchEnvValues(repoRoot, vars) {
|
|
|
4134
4177
|
await writeFile7(target, existing + prefix + lines, "utf8");
|
|
4135
4178
|
return missing.map((v) => v.name);
|
|
4136
4179
|
}
|
|
4180
|
+
async function listChangedFiles(worktreePath) {
|
|
4181
|
+
const raw = await git(["-C", worktreePath, "status", "--porcelain", "-z"]);
|
|
4182
|
+
const entries = raw.split("\0");
|
|
4183
|
+
const files = [];
|
|
4184
|
+
for (let i = 0; i < entries.length; i += 1) {
|
|
4185
|
+
const entry = entries[i];
|
|
4186
|
+
if (!entry) continue;
|
|
4187
|
+
files.push(entry.slice(3));
|
|
4188
|
+
if (["R", "C"].includes(entry[0]) || ["R", "C"].includes(entry[1])) i += 1;
|
|
4189
|
+
}
|
|
4190
|
+
return files;
|
|
4191
|
+
}
|
|
4137
4192
|
function normalizeFindingPaths(findings) {
|
|
4138
4193
|
return {
|
|
4139
4194
|
...findings,
|
|
4140
4195
|
ingestionAnalysis: findings.ingestionAnalysis?.map((e) => ({
|
|
4141
4196
|
...e,
|
|
4142
|
-
paths: e.paths.map(
|
|
4197
|
+
paths: e.paths.map(toWorktreeRelative)
|
|
4143
4198
|
})),
|
|
4144
4199
|
searchImplementationAnalysis: findings.searchImplementationAnalysis ? normalizeSearchLocation(findings.searchImplementationAnalysis) : void 0,
|
|
4145
4200
|
confirmedEntities: findings.confirmedEntities?.map((e) => ({
|
|
4146
4201
|
...e,
|
|
4147
|
-
paths: e.paths.map(
|
|
4202
|
+
paths: e.paths.map(toWorktreeRelative)
|
|
4148
4203
|
}))
|
|
4149
4204
|
};
|
|
4150
4205
|
}
|
|
4151
4206
|
function normalizeSearchLocation(path) {
|
|
4152
|
-
const normalized = path ?
|
|
4207
|
+
const normalized = path ? toWorktreeRelative(path).trim() : "";
|
|
4153
4208
|
return normalized && normalized.toLowerCase() !== "unknown" ? normalized : void 0;
|
|
4154
4209
|
}
|
|
4155
|
-
function
|
|
4210
|
+
function toWorktreeRelative(p) {
|
|
4156
4211
|
return p.replace(/^\/+/, "");
|
|
4157
4212
|
}
|
|
4213
|
+
async function confirmDirtyWorkingTree(ctx, repoRoot) {
|
|
4214
|
+
const MAX_LISTED_DIRTY_FILES = 10;
|
|
4215
|
+
const dirty = await listChangedFiles(repoRoot);
|
|
4216
|
+
const shown = dirty.slice(0, MAX_LISTED_DIRTY_FILES);
|
|
4217
|
+
const overflow = dirty.length - shown.length;
|
|
4218
|
+
const answer = await ctx.requestUserInput({
|
|
4219
|
+
prompt: "Proceed using HEAD only? Uncommitted changes will NOT be included in the generated implementation.",
|
|
4220
|
+
promptType: "acceptReject",
|
|
4221
|
+
options: [],
|
|
4222
|
+
messages: [
|
|
4223
|
+
`${dirty.length} uncommitted change(s) detected. The wizard builds an isolated worktree from HEAD, so these are ignored:`,
|
|
4224
|
+
...shown.map((file) => ` \u2022 ${file}`),
|
|
4225
|
+
...overflow > 0 ? [` \u2022 \u2026and ${overflow} more`] : [],
|
|
4226
|
+
"Commit or stash them first to include them in the implementation."
|
|
4227
|
+
]
|
|
4228
|
+
});
|
|
4229
|
+
if (answer !== true) {
|
|
4230
|
+
throw new Error(
|
|
4231
|
+
"implement aborted: commit or stash your changes so they are built into the worktree, then re-run the wizard."
|
|
4232
|
+
);
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4158
4235
|
|
|
4159
4236
|
// src/lib/algoliaDocs.ts
|
|
4160
4237
|
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
@@ -4214,6 +4291,11 @@ function getFrameworkSpecificDoc(frameworks) {
|
|
|
4214
4291
|
return loadAlgoliaDoc("js");
|
|
4215
4292
|
}
|
|
4216
4293
|
|
|
4294
|
+
// src/lib/shell.ts
|
|
4295
|
+
function shellQuote(value) {
|
|
4296
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
4297
|
+
}
|
|
4298
|
+
|
|
4217
4299
|
// src/actions/resolveEnvVarPrefix.ts
|
|
4218
4300
|
import z28 from "zod";
|
|
4219
4301
|
var resolveEnvVarPrefixSchema = z28.object({
|
|
@@ -4233,7 +4315,9 @@ var resolveEnvVarPrefix = (frameworkName) => runAgent({
|
|
|
4233
4315
|
|
|
4234
4316
|
// src/actions/implement.ts
|
|
4235
4317
|
var implementSchema = z29.object({
|
|
4318
|
+
filesChanged: z29.array(z29.string()),
|
|
4236
4319
|
summary: z29.string(),
|
|
4320
|
+
worktreePath: z29.string().optional(),
|
|
4237
4321
|
ingestCommand: z29.string().optional(),
|
|
4238
4322
|
ingestScriptRan: z29.boolean().optional(),
|
|
4239
4323
|
ingestRecordCount: z29.number().optional(),
|
|
@@ -4286,6 +4370,8 @@ function frameworksForDoc(language) {
|
|
|
4286
4370
|
}
|
|
4287
4371
|
function baseInstructions(input) {
|
|
4288
4372
|
return [
|
|
4373
|
+
// Agents have renamed this (e.g. appending the project name), which the
|
|
4374
|
+
// index-scoped keys then reject with a 403.
|
|
4289
4375
|
`Target Algolia index, to be used exactly as written \u2014 never renamed, re-cased, prefixed, or suffixed: "${input.targetIndex}"`,
|
|
4290
4376
|
`Project languages and frameworks: ${JSON.stringify(input.language)}`,
|
|
4291
4377
|
"Make minimal, idiomatic changes; do not touch unrelated code.",
|
|
@@ -4303,14 +4389,14 @@ function sourceSpecificInstructions(input) {
|
|
|
4303
4389
|
"Add env vars for any local source access (DB URL, API base, file paths) that is not a fixed repo path."
|
|
4304
4390
|
],
|
|
4305
4391
|
fileUpload: [
|
|
4306
|
-
`Records come from the developer's file, already copied into the
|
|
4392
|
+
`Records come from the developer's file, already copied into the worktree at "${input.uploadFilePath}". Read and parse that exact file.`,
|
|
4307
4393
|
"Parse by extension: JSON = array of objects; CSV/TSV = header row maps to keys.",
|
|
4308
4394
|
"Map parsed columns/fields to the confirmed entity attributes.",
|
|
4309
4395
|
"Never fabricate, hardcode, or substitute a different file."
|
|
4310
4396
|
],
|
|
4311
4397
|
generated: [
|
|
4312
4398
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
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.",
|
|
4399
|
+
"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.",
|
|
4314
4400
|
"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.",
|
|
4315
4401
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
4316
4402
|
]
|
|
@@ -4359,11 +4445,14 @@ function searchInstructions(input) {
|
|
|
4359
4445
|
"If a search box already exists, replace it with yours.",
|
|
4360
4446
|
`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.`,
|
|
4361
4447
|
"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.",
|
|
4362
|
-
// The key is provisioned only after verification passes,
|
|
4363
|
-
//
|
|
4364
|
-
//
|
|
4448
|
+
// The key is provisioned only after verification passes, so the agent never
|
|
4449
|
+
// sees one. It must also leave .env alone: the wizard reads that file to
|
|
4450
|
+
// decide whether a key already exists, and an agent-invented value there
|
|
4451
|
+
// would be reused as if it were real.
|
|
4365
4452
|
`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.`,
|
|
4366
|
-
//
|
|
4453
|
+
// Not the agent's to rename: the wizard writes these exact names into
|
|
4454
|
+
// ".env" right after this step, so a renamed prefix would leave the code
|
|
4455
|
+
// reading a var the wizard never wrote.
|
|
4367
4456
|
`Use exactly these env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
4368
4457
|
"Install any Algolia packages you import with the project's own package manager via runShell, and declare them in the project's dependency manifest.",
|
|
4369
4458
|
"Match the styles of the application as closely as possible.",
|
|
@@ -4372,10 +4461,10 @@ function searchInstructions(input) {
|
|
|
4372
4461
|
}
|
|
4373
4462
|
function verificationInstructions(input) {
|
|
4374
4463
|
return [
|
|
4375
|
-
"Verify the Algolia implementation changes.",
|
|
4464
|
+
"Verify the Algolia implementation changes in the current worktree.",
|
|
4376
4465
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
4377
4466
|
"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.",
|
|
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.",
|
|
4467
|
+
"This worktree starts with no installed dependencies. If a check fails because packages or modules are missing, install the dependencies via runShell and re-run it rather than changing the code.",
|
|
4379
4468
|
"For issues caused by the implementation, make minimal fixes with writeFile and re-run the checks.",
|
|
4380
4469
|
"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.",
|
|
4381
4470
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
@@ -4470,11 +4559,11 @@ function ingestFailure(attempt, executions) {
|
|
|
4470
4559
|
}
|
|
4471
4560
|
return { reason: `it failed (exit ${attempt.exitCode ?? "unknown"})`, detail };
|
|
4472
4561
|
}
|
|
4473
|
-
function makeToolContext(
|
|
4562
|
+
function makeToolContext(worktree, env = async () => ({})) {
|
|
4474
4563
|
return createToolContext(
|
|
4475
4564
|
DEFAULT_TOOL_LIMITS,
|
|
4476
|
-
|
|
4477
|
-
createShellContext({ env, approve: storeApproval(
|
|
4565
|
+
worktree,
|
|
4566
|
+
createShellContext({ env, approve: storeApproval(worktree) })
|
|
4478
4567
|
);
|
|
4479
4568
|
}
|
|
4480
4569
|
function verificationRetryInstructions(verification) {
|
|
@@ -4482,7 +4571,7 @@ function verificationRetryInstructions(verification) {
|
|
|
4482
4571
|
`Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
|
|
4483
4572
|
];
|
|
4484
4573
|
}
|
|
4485
|
-
async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
4574
|
+
async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWorktreePath) {
|
|
4486
4575
|
const repoRoot = process.cwd();
|
|
4487
4576
|
const scan = ctx.getStepOutput("project-scan");
|
|
4488
4577
|
const entities = ctx.getStepOutput(
|
|
@@ -4563,6 +4652,9 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
|
4563
4652
|
const targetIndex = selected?.selection;
|
|
4564
4653
|
useWizard.getState().setTargetIndex(targetIndex ?? null);
|
|
4565
4654
|
await assertGitRepoWithHead(repoRoot);
|
|
4655
|
+
if (await isWorkingTreeDirty(repoRoot)) {
|
|
4656
|
+
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
4657
|
+
}
|
|
4566
4658
|
const normalized = normalizeFindingPaths(findings);
|
|
4567
4659
|
const confirmed2 = normalized.confirmedEntities;
|
|
4568
4660
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
@@ -4574,303 +4666,328 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
|
4574
4666
|
if (useCases.includes("ingestion")) {
|
|
4575
4667
|
ingestAppId = appId ?? (await requireApplication()).id;
|
|
4576
4668
|
}
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
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"
|
|
4593
|
-
);
|
|
4594
|
-
}
|
|
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(
|
|
4669
|
+
const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
|
|
4670
|
+
try {
|
|
4671
|
+
process.chdir(worktree);
|
|
4672
|
+
let uploadFilePath;
|
|
4673
|
+
let uploadWarning;
|
|
4674
|
+
if (ingestionSource === "fileUpload") {
|
|
4675
|
+
const copied = await copyUploadIntoWorktree(
|
|
4622
4676
|
repoRoot,
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
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"
|
|
4677
|
+
worktree,
|
|
4678
|
+
INGEST_DIR,
|
|
4679
|
+
uploadSourcePath ?? ""
|
|
4635
4680
|
);
|
|
4681
|
+
if (copied.ok) {
|
|
4682
|
+
uploadFilePath = copied.relPath;
|
|
4683
|
+
} else {
|
|
4684
|
+
ingestionSource = "generated";
|
|
4685
|
+
uploadWarning = `\u26A0\uFE0F Could not use the uploaded file (${copied.reason}); generating sample records instead.`;
|
|
4686
|
+
logger.warn(
|
|
4687
|
+
{ reason: copied.reason },
|
|
4688
|
+
"implement: file upload unavailable; falling back to generated sample records"
|
|
4689
|
+
);
|
|
4690
|
+
}
|
|
4636
4691
|
}
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
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
|
|
4692
|
+
const publicEnvVarPrefix = await publicEnvVarPrefixPromise;
|
|
4693
|
+
const input = {
|
|
4694
|
+
findings: normalized,
|
|
4695
|
+
confirmed: confirmed2,
|
|
4696
|
+
searchLocation,
|
|
4697
|
+
targetIndex,
|
|
4698
|
+
language,
|
|
4699
|
+
publicEnvVarPrefix,
|
|
4700
|
+
appId,
|
|
4701
|
+
searchEnvVars: publicSearchEnvVars(
|
|
4702
|
+
publicEnvVarPrefix,
|
|
4703
|
+
targetIndex,
|
|
4704
|
+
appId
|
|
4660
4705
|
),
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
if (
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
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"
|
|
4706
|
+
ingestDir: INGEST_DIR,
|
|
4707
|
+
ingestionSource,
|
|
4708
|
+
uploadFilePath,
|
|
4709
|
+
searchUiTarget: searchUiTarget(language)
|
|
4710
|
+
};
|
|
4711
|
+
const summaries = [];
|
|
4712
|
+
if (uploadWarning) summaries.push(uploadWarning);
|
|
4713
|
+
let envSearchKey;
|
|
4714
|
+
let envAppIdMismatch = false;
|
|
4715
|
+
if (useCases.includes("search") && appId) {
|
|
4716
|
+
const envAppId = await readEnvVar(
|
|
4717
|
+
worktree,
|
|
4718
|
+
publicAppIdVar(publicEnvVarPrefix)
|
|
4698
4719
|
);
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4720
|
+
if (envAppId === appId) {
|
|
4721
|
+
envSearchKey = await readEnvVar(
|
|
4722
|
+
worktree,
|
|
4723
|
+
publicSearchKeyVar(publicEnvVarPrefix)
|
|
4724
|
+
);
|
|
4725
|
+
} else if (envAppId) {
|
|
4726
|
+
envAppIdMismatch = true;
|
|
4727
|
+
const appIdVarName = publicAppIdVar(publicEnvVarPrefix);
|
|
4728
|
+
const searchKeyVarName = publicSearchKeyVar(publicEnvVarPrefix);
|
|
4729
|
+
summaries.push(
|
|
4730
|
+
`\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.`
|
|
4731
|
+
);
|
|
4732
|
+
logger.warn(
|
|
4733
|
+
{ envAppId, appId },
|
|
4734
|
+
"implement: .env holds credentials for a different Algolia application; not reusing its search key"
|
|
4735
|
+
);
|
|
4708
4736
|
}
|
|
4709
|
-
}
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4737
|
+
}
|
|
4738
|
+
let finalSearchEnvVars = input.searchEnvVars;
|
|
4739
|
+
let agentRuns = 0;
|
|
4740
|
+
let ingestCommand;
|
|
4741
|
+
let ingestScriptRan = false;
|
|
4742
|
+
let ingestRecordCount;
|
|
4743
|
+
let ingestDurationMs;
|
|
4744
|
+
let ingestOutcomeMessage;
|
|
4745
|
+
const ingestKeyAppId = ingestAppId;
|
|
4746
|
+
const ingestionTools = ingestKeyAppId ? makeToolContext(worktree, async () => ({
|
|
4747
|
+
[APP_ID_VAR]: ingestKeyAppId,
|
|
4748
|
+
[API_KEY_VAR]: (await resolveWriteKey(targetIndex, ingestKeyAppId)).key,
|
|
4749
|
+
[INDEX_NAME_VAR]: targetIndex
|
|
4750
|
+
})) : void 0;
|
|
4751
|
+
const searchTools = makeToolContext(worktree);
|
|
4752
|
+
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
4753
|
+
if (agentRuns > 0) ctx.recordStepExecution();
|
|
4754
|
+
agentRuns += 1;
|
|
4755
|
+
return runAgent({
|
|
4756
|
+
instructions: buildAgentInstructions(
|
|
4757
|
+
currentUseCase,
|
|
4758
|
+
input,
|
|
4759
|
+
extraInstructions
|
|
4760
|
+
),
|
|
4761
|
+
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
4762
|
+
outputSchema: implementationOutputSchema,
|
|
4763
|
+
toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
|
|
4732
4764
|
});
|
|
4733
4765
|
}
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4766
|
+
async function runVerificationUseCase() {
|
|
4767
|
+
if (agentRuns > 0) ctx.recordStepExecution();
|
|
4768
|
+
agentRuns += 1;
|
|
4769
|
+
return runAgent({
|
|
4770
|
+
instructions: buildAgentInstructions("verification", input),
|
|
4771
|
+
tools: toolsForUseCase("verification"),
|
|
4772
|
+
outputSchema: verificationOutputSchema,
|
|
4773
|
+
toolContext: searchTools
|
|
4774
|
+
});
|
|
4775
|
+
}
|
|
4776
|
+
if (useCases.includes("ingestion")) {
|
|
4777
|
+
let ingestFailureDetail;
|
|
4778
|
+
const result = await runImplementationUseCase("ingestion");
|
|
4779
|
+
summaries.push(formatSummary("ingestion", result.summary));
|
|
4780
|
+
ingestCommand = result.ingestCommand;
|
|
4781
|
+
const ingestionContext = ingestionTools ?? searchTools;
|
|
4782
|
+
const executions = ingestionContext.shell.executions;
|
|
4783
|
+
const {
|
|
4784
|
+
run: ingestRun,
|
|
4785
|
+
attempt: ingestAttempt,
|
|
4786
|
+
recordCount
|
|
4787
|
+
} = ingestOutcome(executions, ingestCommand);
|
|
4788
|
+
ingestScriptRan = ingestRun != null;
|
|
4789
|
+
ingestRecordCount = recordCount;
|
|
4790
|
+
ingestDurationMs = ingestRun?.durationMs;
|
|
4791
|
+
if (ingestScriptRan && ingestionContext.reviewed.length === 0) {
|
|
4792
|
+
summaries.push(
|
|
4793
|
+
"\u26A0\uFE0F The ingestion script ran without being shown to you for review. Read it in the worktree before trusting the index contents."
|
|
4760
4794
|
);
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
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"
|
|
4779
|
-
});
|
|
4780
|
-
break;
|
|
4781
|
-
}
|
|
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}`
|
|
4795
|
+
logger.warn(
|
|
4796
|
+
{ ingestCommand },
|
|
4797
|
+
"implement: ingestion ran without a reviewScript call"
|
|
4786
4798
|
);
|
|
4787
4799
|
}
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
);
|
|
4799
|
-
|
|
4800
|
+
if (ingestScriptRan) {
|
|
4801
|
+
ingestOutcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
|
|
4802
|
+
if (ingestRecordCount != null) {
|
|
4803
|
+
track("AI Wizard Ingest Successful", {
|
|
4804
|
+
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
4805
|
+
record_count: ingestRecordCount,
|
|
4806
|
+
duration_ms: ingestDurationMs ?? 0
|
|
4807
|
+
});
|
|
4808
|
+
}
|
|
4809
|
+
} else {
|
|
4810
|
+
const { reason, detail } = ingestFailure(ingestAttempt, executions);
|
|
4811
|
+
ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
|
|
4812
|
+
ingestFailureDetail = detail;
|
|
4800
4813
|
summaries.push(
|
|
4801
|
-
|
|
4814
|
+
`\u26A0\uFE0F Ingestion did not complete: ${reason}.${detail ? `
|
|
4815
|
+
${detail}` : ""}`
|
|
4802
4816
|
);
|
|
4803
|
-
} catch (err) {
|
|
4804
|
-
searchKeyError = err.message;
|
|
4805
4817
|
logger.warn(
|
|
4806
|
-
{
|
|
4807
|
-
|
|
4818
|
+
{
|
|
4819
|
+
ingestCommand,
|
|
4820
|
+
reason,
|
|
4821
|
+
approved: ingestAttempt?.approved,
|
|
4822
|
+
exitCode: ingestAttempt?.exitCode,
|
|
4823
|
+
timedOut: ingestAttempt?.timedOut,
|
|
4824
|
+
commandsRun: executions.length
|
|
4825
|
+
},
|
|
4826
|
+
"implement: ingestion script did not complete successfully"
|
|
4808
4827
|
);
|
|
4828
|
+
track("Error", {
|
|
4829
|
+
step: "Push Data",
|
|
4830
|
+
error: `ingestion did not complete: ${reason}`,
|
|
4831
|
+
product_area: "AI Wizard"
|
|
4832
|
+
});
|
|
4809
4833
|
}
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
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
|
|
4824
|
-
);
|
|
4825
|
-
if (written.length > 0) {
|
|
4826
|
-
summaries.push(`Wrote ${written.join(", ")} to .env.`);
|
|
4834
|
+
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
4835
|
+
if (ingestCommand) {
|
|
4836
|
+
commandMessages.push(`Ingestion command: ${ingestCommand}`);
|
|
4827
4837
|
}
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4838
|
+
await ctx.requestUserInput({
|
|
4839
|
+
prompt: "",
|
|
4840
|
+
promptType: "enterToContinue",
|
|
4841
|
+
options: [],
|
|
4842
|
+
// The wizard never streams command output, so a failed run's tail is the
|
|
4843
|
+
// only place the developer sees why it failed. One message per line:
|
|
4844
|
+
// the panel's height accounting counts a message as one wrapped line
|
|
4845
|
+
// (see Notices.tsx), so an embedded newline overflows it.
|
|
4846
|
+
messages: [
|
|
4847
|
+
ingestOutcomeMessage,
|
|
4848
|
+
...ingestFailureDetail?.split("\n").filter((l) => l.trim()) ?? [],
|
|
4849
|
+
...commandMessages
|
|
4850
|
+
]
|
|
4851
|
+
});
|
|
4852
|
+
}
|
|
4853
|
+
if (useCases.includes("search")) {
|
|
4854
|
+
let extraInstructions = [];
|
|
4855
|
+
const preSearchFiles = new Set(await listChangedFiles(worktree));
|
|
4856
|
+
for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
|
|
4857
|
+
if (attempt > 1) {
|
|
4858
|
+
logger.info(
|
|
4859
|
+
{
|
|
4860
|
+
attempt,
|
|
4861
|
+
maxAttempts: MAX_IMPLEMENT_VERIFICATION_ATTEMPTS,
|
|
4862
|
+
extraInstructions
|
|
4863
|
+
},
|
|
4864
|
+
"implement: retrying search implementation after failed verification"
|
|
4865
|
+
);
|
|
4866
|
+
}
|
|
4867
|
+
const { summary } = await runImplementationUseCase(
|
|
4868
|
+
"search",
|
|
4869
|
+
extraInstructions
|
|
4834
4870
|
);
|
|
4871
|
+
summaries.push(formatSummary("search", summary));
|
|
4872
|
+
const verification = await runVerificationUseCase();
|
|
4873
|
+
summaries.push(formatSummary("verification", verification.summary));
|
|
4874
|
+
if (verification.sufficient) {
|
|
4875
|
+
ctx.setUserInput("implementation", "success");
|
|
4876
|
+
const searchFilesChanged = (await listChangedFiles(worktree)).filter(
|
|
4877
|
+
(file) => !preSearchFiles.has(file)
|
|
4878
|
+
);
|
|
4879
|
+
track("AI Wizard Frontend Component Generated", {
|
|
4880
|
+
filePaths: searchFilesChanged
|
|
4881
|
+
});
|
|
4882
|
+
track("AI Wizard Wired to UI", {
|
|
4883
|
+
location_heuristic: searchLocation ?? "unknown"
|
|
4884
|
+
});
|
|
4885
|
+
break;
|
|
4886
|
+
}
|
|
4887
|
+
if (attempt === MAX_IMPLEMENT_VERIFICATION_ATTEMPTS) {
|
|
4888
|
+
ctx.setUserInput("implementation", "fail");
|
|
4889
|
+
throw new Error(
|
|
4890
|
+
`Implementation verification failed after ${MAX_IMPLEMENT_VERIFICATION_ATTEMPTS} attempts: ${verification.additionalInstructions ?? verification.summary}`
|
|
4891
|
+
);
|
|
4892
|
+
}
|
|
4893
|
+
extraInstructions = verificationRetryInstructions(verification);
|
|
4835
4894
|
}
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4895
|
+
let searchKey;
|
|
4896
|
+
let searchKeyError;
|
|
4897
|
+
if (appId) {
|
|
4898
|
+
try {
|
|
4899
|
+
const resolved2 = await resolveSearchOnlyKey(
|
|
4900
|
+
targetIndex,
|
|
4901
|
+
appId,
|
|
4902
|
+
envSearchKey
|
|
4903
|
+
);
|
|
4904
|
+
searchKey = resolved2.key;
|
|
4905
|
+
summaries.push(
|
|
4906
|
+
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}.`
|
|
4907
|
+
);
|
|
4908
|
+
} catch (err) {
|
|
4909
|
+
searchKeyError = err.message;
|
|
4910
|
+
logger.warn(
|
|
4911
|
+
{ err: searchKeyError },
|
|
4912
|
+
"implement: could not provision a search-only API key; the .env value stays a placeholder"
|
|
4913
|
+
);
|
|
4914
|
+
}
|
|
4841
4915
|
}
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4916
|
+
finalSearchEnvVars = publicSearchEnvVars(
|
|
4917
|
+
publicEnvVarPrefix,
|
|
4918
|
+
targetIndex,
|
|
4919
|
+
appId,
|
|
4920
|
+
searchKey
|
|
4921
|
+
);
|
|
4922
|
+
const resolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4923
|
+
(v) => !v.value.startsWith("<")
|
|
4924
|
+
);
|
|
4925
|
+
if (resolvedSearchEnvVars.length > 0) {
|
|
4926
|
+
const written = await writeSearchEnvValues(
|
|
4927
|
+
worktree,
|
|
4928
|
+
resolvedSearchEnvVars
|
|
4845
4929
|
);
|
|
4846
|
-
|
|
4847
|
-
{
|
|
4848
|
-
|
|
4930
|
+
if (written.length > 0) {
|
|
4931
|
+
summaries.push(`Wrote ${written.join(", ")} to .env.`);
|
|
4932
|
+
}
|
|
4933
|
+
const ignored = await ensureGitIgnored(worktree, join12(worktree, ".env"));
|
|
4934
|
+
if (ignored === "added") {
|
|
4935
|
+
summaries.push("Added .env to .gitignore.");
|
|
4936
|
+
} else if (ignored === "tracked") {
|
|
4937
|
+
summaries.push(
|
|
4938
|
+
'\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.'
|
|
4939
|
+
);
|
|
4940
|
+
}
|
|
4941
|
+
const stale = [];
|
|
4942
|
+
for (const v of resolvedSearchEnvVars) {
|
|
4943
|
+
if (written.includes(v.name)) continue;
|
|
4944
|
+
const current = await readEnvVar(worktree, v.name);
|
|
4945
|
+
if (current && current !== v.value) stale.push(v);
|
|
4946
|
+
}
|
|
4947
|
+
if (stale.length > 0 && !envAppIdMismatch) {
|
|
4948
|
+
summaries.push(
|
|
4949
|
+
`\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.`
|
|
4950
|
+
);
|
|
4951
|
+
logger.warn(
|
|
4952
|
+
{ vars: stale.map((v) => v.name) },
|
|
4953
|
+
"implement: .env holds different values for the resolved search credentials; not overwriting them"
|
|
4954
|
+
);
|
|
4955
|
+
}
|
|
4956
|
+
}
|
|
4957
|
+
const unresolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4958
|
+
(v) => v.value.startsWith("<")
|
|
4959
|
+
);
|
|
4960
|
+
if (unresolvedSearchEnvVars.length > 0) {
|
|
4961
|
+
summaries.push(
|
|
4962
|
+
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.` + // Without the reason the line is a dead end.
|
|
4963
|
+
(searchKeyError ? ` Reason: ${searchKeyError}` : "")
|
|
4849
4964
|
);
|
|
4850
4965
|
}
|
|
4966
|
+
} else {
|
|
4967
|
+
ctx.setUserInput("implementation", "success");
|
|
4851
4968
|
}
|
|
4852
|
-
const
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
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}` : "")
|
|
4969
|
+
const filesChanged = await listChangedFiles(worktree);
|
|
4970
|
+
if (filesChanged.length === 0) {
|
|
4971
|
+
logger.warn(
|
|
4972
|
+
"implement: agent reported success but no files changed in the worktree"
|
|
4858
4973
|
);
|
|
4859
4974
|
}
|
|
4860
|
-
|
|
4861
|
-
|
|
4975
|
+
return {
|
|
4976
|
+
ingestionSource,
|
|
4977
|
+
filesChanged,
|
|
4978
|
+
summary: summaries.join("\n\n"),
|
|
4979
|
+
worktreePath: worktree,
|
|
4980
|
+
...useCases.includes("ingestion") && ingestCommand ? {
|
|
4981
|
+
ingestCommand,
|
|
4982
|
+
ingestScriptRan,
|
|
4983
|
+
...ingestRecordCount != null ? { ingestRecordCount } : {},
|
|
4984
|
+
...ingestDurationMs != null ? { ingestDurationMs } : {}
|
|
4985
|
+
} : {},
|
|
4986
|
+
...useCases.includes("search") ? { searchEnvVars: finalSearchEnvVars } : {}
|
|
4987
|
+
};
|
|
4988
|
+
} finally {
|
|
4989
|
+
process.chdir(repoRoot);
|
|
4862
4990
|
}
|
|
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
|
-
};
|
|
4874
4991
|
}
|
|
4875
4992
|
|
|
4876
4993
|
// src/workflows/default.ts
|
|
@@ -4942,7 +5059,10 @@ var defaultWorkflow = {
|
|
|
4942
5059
|
ctx.notify({
|
|
4943
5060
|
messages: ["Building your Algolia search experience\u2026"]
|
|
4944
5061
|
});
|
|
4945
|
-
|
|
5062
|
+
const ingestion2 = ctx.getStepOutput(
|
|
5063
|
+
"ingestion"
|
|
5064
|
+
);
|
|
5065
|
+
return implement(ctx, ["search"], ingestion2?.worktreePath);
|
|
4946
5066
|
}
|
|
4947
5067
|
}),
|
|
4948
5068
|
defineStep({
|
|
@@ -4957,8 +5077,9 @@ var defaultWorkflow = {
|
|
|
4957
5077
|
"ingestion"
|
|
4958
5078
|
);
|
|
4959
5079
|
return reviewStep(ctx, {
|
|
4960
|
-
//
|
|
4961
|
-
//
|
|
5080
|
+
// The ingestion step already showed the user the exact `ingestCommand`
|
|
5081
|
+
// and worktree path as a notice, so nextSteps must not restate it —
|
|
5082
|
+
// an LLM-paraphrased command risks being wrong.
|
|
4962
5083
|
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."
|
|
4963
5084
|
});
|
|
4964
5085
|
}
|
|
@@ -5005,6 +5126,7 @@ var selectIndex = {
|
|
|
5005
5126
|
selection: "wizard_seed_products"
|
|
5006
5127
|
};
|
|
5007
5128
|
var ingestion = {
|
|
5129
|
+
filesChanged: ["algolia/ingest.mjs", "algolia/records.json", "package.json"],
|
|
5008
5130
|
summary: "Generated sample Product records and an ingestion script that pushes them to the target index with algoliasearch.",
|
|
5009
5131
|
ingestCommand: "node algolia/ingest.mjs",
|
|
5010
5132
|
ingestScriptRan: true,
|
|
@@ -5016,6 +5138,7 @@ var confirmFramework2 = {
|
|
|
5016
5138
|
frameworks: projectScan2.frameworks
|
|
5017
5139
|
};
|
|
5018
5140
|
var search = {
|
|
5141
|
+
filesChanged: ["src/components/Search.tsx", "src/components/Header.tsx", ".env"],
|
|
5019
5142
|
summary: "Added an InstantSearch-powered search box and results list, mounted in the shared header component.",
|
|
5020
5143
|
ingestionSource: "generated",
|
|
5021
5144
|
searchEnvVars: [
|
|
@@ -5028,7 +5151,7 @@ var review = {
|
|
|
5028
5151
|
"Ingested 25 generated Product records into wizard_seed_products.",
|
|
5029
5152
|
"Added an InstantSearch search experience to the shared header."
|
|
5030
5153
|
],
|
|
5031
|
-
reviewPrompt: "Review the Algolia ingestion and search changes.",
|
|
5154
|
+
reviewPrompt: "Review the Algolia ingestion and search changes in this worktree.",
|
|
5032
5155
|
nextSteps: ["Point the ingestion script at your real product data."]
|
|
5033
5156
|
};
|
|
5034
5157
|
var SEEDS = {
|
|
@@ -5145,9 +5268,9 @@ Options:
|
|
|
5145
5268
|
steps pre-filled with test data. Pass with no value to print
|
|
5146
5269
|
the step ids. See CONTRIBUTING.md.
|
|
5147
5270
|
--no-telemetry Send no telemetry or analytics for this run.
|
|
5148
|
-
--reset-on-run Wipe this project's wizard state (run state, AI consent
|
|
5149
|
-
before starting, so the run behaves like a
|
|
5150
|
-
run. Also drops every API key the wizard has
|
|
5271
|
+
--reset-on-run Wipe this project's wizard state (run state, AI consent,
|
|
5272
|
+
worktrees) before starting, so the run behaves like a
|
|
5273
|
+
first-ever run. Also drops every API key the wizard has
|
|
5151
5274
|
stored in your keychain (or, where the platform has none,
|
|
5152
5275
|
the encrypted file it falls back to \u2014 see CONTRIBUTING.md),
|
|
5153
5276
|
for this project and any other, so later runs create new
|
|
@@ -5187,7 +5310,7 @@ function parseCliArgs(argv) {
|
|
|
5187
5310
|
}
|
|
5188
5311
|
|
|
5189
5312
|
// src/lib/resetState.ts
|
|
5190
|
-
import { readdir as
|
|
5313
|
+
import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
5191
5314
|
import { join as join13 } from "node:path";
|
|
5192
5315
|
var KEEP = ["wizard.log"];
|
|
5193
5316
|
async function resetProjectState() {
|
|
@@ -5195,7 +5318,7 @@ async function resetProjectState() {
|
|
|
5195
5318
|
await forgetResolvedKeys();
|
|
5196
5319
|
let entries;
|
|
5197
5320
|
try {
|
|
5198
|
-
entries = await
|
|
5321
|
+
entries = await readdir4(dir);
|
|
5199
5322
|
} catch {
|
|
5200
5323
|
return { dir, removed: [] };
|
|
5201
5324
|
}
|