@algolia/wizard 0.5.0-rc.50.20 → 0.6.0-rc.51.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +1231 -415
- package/docs/algolia-sdk/README.md +50 -22
- package/docs/algolia-sdk/instantsearch-setup-templates.md +92 -0
- package/docs/algolia-sdk/save-records-csharp.md +71 -0
- package/docs/algolia-sdk/save-records-dart.md +74 -0
- package/docs/algolia-sdk/save-records-go.md +62 -0
- package/docs/algolia-sdk/save-records-java.md +66 -0
- package/docs/algolia-sdk/save-records-kotlin.md +60 -0
- package/docs/algolia-sdk/save-records-php.md +50 -0
- package/docs/algolia-sdk/save-records-python.md +51 -0
- package/docs/algolia-sdk/save-records-ruby.md +48 -0
- package/docs/algolia-sdk/save-records-scala.md +68 -0
- package/docs/algolia-sdk/save-records-swift.md +88 -0
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { render } from "ink";
|
|
5
5
|
|
|
6
6
|
// src/ui/App.tsx
|
|
7
|
-
import { Box as Box13, Text as Text13, useApp, useInput as useInput6, useWindowSize as
|
|
7
|
+
import { Box as Box13, Text as Text13, useApp, useInput as useInput6, useWindowSize as useWindowSize7 } from "ink";
|
|
8
8
|
|
|
9
9
|
// src/core/store.ts
|
|
10
10
|
import { create } from "zustand";
|
|
@@ -525,10 +525,20 @@ function NextAction({
|
|
|
525
525
|
}
|
|
526
526
|
|
|
527
527
|
// src/ui/SelectPrompt.tsx
|
|
528
|
-
import { Box as Box4, Text as Text4, useInput } from "ink";
|
|
529
|
-
import { useState as useState3 } from "react";
|
|
528
|
+
import { Box as Box4, Text as Text4, measureElement as measureElement2, useInput, useWindowSize as useWindowSize3 } from "ink";
|
|
529
|
+
import { useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
|
|
530
530
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
531
531
|
var CANCEL = "cancel";
|
|
532
|
+
var ARROW_WIDTH = 4;
|
|
533
|
+
var COLUMN_GAP = 2;
|
|
534
|
+
var BAR_PADDING = 2;
|
|
535
|
+
function fittedWidth(node, columns) {
|
|
536
|
+
let left = 0;
|
|
537
|
+
for (let n = node; n; n = n.parentNode) {
|
|
538
|
+
left += n.yogaNode?.getComputedLeft() ?? 0;
|
|
539
|
+
}
|
|
540
|
+
return Math.max(Math.min(measureElement2(node).width, columns - left), 0);
|
|
541
|
+
}
|
|
532
542
|
function SelectPrompt({
|
|
533
543
|
options,
|
|
534
544
|
onSelect,
|
|
@@ -553,12 +563,29 @@ function SelectPrompt({
|
|
|
553
563
|
if (rows.length > 1) hints.push({ key: "[\u2191] [\u2193]", label: "move" });
|
|
554
564
|
if (multi) hints.push({ key: "[space]", label: "select" });
|
|
555
565
|
hints.push({ key: "[enter]", label: "confirm" });
|
|
556
|
-
const
|
|
557
|
-
const
|
|
566
|
+
const containerRef = useRef2(null);
|
|
567
|
+
const { columns } = useWindowSize3();
|
|
568
|
+
const [width, setWidth] = useState3(columns);
|
|
569
|
+
useLayoutEffect(() => {
|
|
570
|
+
if (containerRef.current) {
|
|
571
|
+
setWidth(fittedWidth(containerRef.current, columns));
|
|
572
|
+
}
|
|
573
|
+
}, [columns]);
|
|
574
|
+
const inner = Math.max(width - BAR_PADDING, 0);
|
|
575
|
+
const labelWidth = Math.min(
|
|
576
|
+
ARROW_WIDTH + (multi ? 2 : 0) + Math.max(0, ...rows.map((opt) => opt.length)) + COLUMN_GAP,
|
|
577
|
+
inner
|
|
578
|
+
);
|
|
579
|
+
const badgeWidth = Math.max(
|
|
558
580
|
0,
|
|
559
|
-
...rows.map((_, i) =>
|
|
581
|
+
...rows.map((_, i) => {
|
|
582
|
+
const s = secondary?.[i];
|
|
583
|
+
return s?.kind === "badge" ? s.value.length : 0;
|
|
584
|
+
})
|
|
560
585
|
);
|
|
561
|
-
const
|
|
586
|
+
const barWidth = Math.min(labelWidth + badgeWidth + BAR_PADDING, width);
|
|
587
|
+
const barLabelWidth = Math.max(barWidth - BAR_PADDING - badgeWidth, 0);
|
|
588
|
+
const textWidth = inner - labelWidth;
|
|
562
589
|
useInput((input, key) => {
|
|
563
590
|
if (rows.length === 0) return;
|
|
564
591
|
if (key.upArrow || input === "k") {
|
|
@@ -582,7 +609,7 @@ function SelectPrompt({
|
|
|
582
609
|
}
|
|
583
610
|
}
|
|
584
611
|
});
|
|
585
|
-
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 1, children: [
|
|
612
|
+
return /* @__PURE__ */ jsx4(Box4, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 1, width, children: [
|
|
586
613
|
error && /* @__PURE__ */ jsx4(Text4, { color: COLORS.danger, children: error }),
|
|
587
614
|
messages?.map((m, i) => /* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
588
615
|
table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
|
|
@@ -596,29 +623,36 @@ function SelectPrompt({
|
|
|
596
623
|
const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
|
|
597
624
|
const sec = isCancel ? void 0 : secondary?.[i];
|
|
598
625
|
const labelColor = highlighted ? COLORS.highlight.fg : void 0;
|
|
599
|
-
const label = /* @__PURE__ */ jsxs3(Text4, { color: labelColor, children: [
|
|
626
|
+
const label = /* @__PURE__ */ jsxs3(Text4, { color: labelColor, wrap: "truncate", children: [
|
|
600
627
|
highlighted ? "\u276F " : " ",
|
|
601
628
|
bullet,
|
|
602
629
|
option
|
|
603
630
|
] });
|
|
631
|
+
const isText = sec?.kind === "text";
|
|
604
632
|
return /* @__PURE__ */ jsxs3(
|
|
605
633
|
Box4,
|
|
606
634
|
{
|
|
607
|
-
width:
|
|
635
|
+
width: isText ? "100%" : barWidth,
|
|
608
636
|
paddingX: 1,
|
|
609
637
|
paddingY: 1,
|
|
610
638
|
backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
|
|
611
639
|
children: [
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
640
|
+
/* @__PURE__ */ jsx4(Box4, { width: isText ? labelWidth : barLabelWidth, children: label }),
|
|
641
|
+
isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box4, { width: textWidth, children: /* @__PURE__ */ jsx4(
|
|
642
|
+
Text4,
|
|
643
|
+
{
|
|
644
|
+
wrap: "truncate",
|
|
645
|
+
color: highlighted ? COLORS.primary : COLORS.muted,
|
|
646
|
+
children: sec.value
|
|
647
|
+
}
|
|
648
|
+
) }),
|
|
649
|
+
sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box4, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text4, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
|
|
616
650
|
]
|
|
617
651
|
},
|
|
618
652
|
`row-${i}`
|
|
619
653
|
);
|
|
620
654
|
}) }),
|
|
621
|
-
/* @__PURE__ */ jsx4(
|
|
655
|
+
/* @__PURE__ */ jsx4(Text4, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs3(Text4, { children: [
|
|
622
656
|
i > 0 ? " " : "",
|
|
623
657
|
/* @__PURE__ */ jsx4(Text4, { color: COLORS.primary, children: key }),
|
|
624
658
|
/* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
|
|
@@ -626,7 +660,7 @@ function SelectPrompt({
|
|
|
626
660
|
label
|
|
627
661
|
] })
|
|
628
662
|
] }, label)) })
|
|
629
|
-
] });
|
|
663
|
+
] }) });
|
|
630
664
|
}
|
|
631
665
|
|
|
632
666
|
// src/ui/PromptInput.tsx
|
|
@@ -749,7 +783,7 @@ function PromptInput() {
|
|
|
749
783
|
// src/ui/Welcome.tsx
|
|
750
784
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
751
785
|
import { fileURLToPath } from "node:url";
|
|
752
|
-
import { Box as Box6, Spacer, Text as Text6, useInput as useInput3, useWindowSize as
|
|
786
|
+
import { Box as Box6, Spacer, Text as Text6, useInput as useInput3, useWindowSize as useWindowSize4 } from "ink";
|
|
753
787
|
|
|
754
788
|
// src/ui/copy/welcome.ts
|
|
755
789
|
var sidebarItems = [
|
|
@@ -762,12 +796,12 @@ var sidebarItems = [
|
|
|
762
796
|
description: "push 100 records to Algolia in seconds"
|
|
763
797
|
},
|
|
764
798
|
{
|
|
765
|
-
title: "detect your
|
|
766
|
-
description: "React, Vue, Angular,
|
|
799
|
+
title: "detect your stack",
|
|
800
|
+
description: "React, Vue, Angular, Rails, Django, Laravel & more"
|
|
767
801
|
},
|
|
768
802
|
{
|
|
769
803
|
title: "scaffold a search UI",
|
|
770
|
-
description: "a styled InstantSearch
|
|
804
|
+
description: "a styled InstantSearch UI, wired into your app or templates"
|
|
771
805
|
},
|
|
772
806
|
{
|
|
773
807
|
title: "ship it",
|
|
@@ -797,7 +831,7 @@ function SidebarItem({
|
|
|
797
831
|
function Welcome() {
|
|
798
832
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
799
833
|
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
800
|
-
const { rows } =
|
|
834
|
+
const { rows } = useWindowSize4();
|
|
801
835
|
useInput3((input) => {
|
|
802
836
|
if (input === " ") confirmStart();
|
|
803
837
|
else if (input === "i") openLearnMore();
|
|
@@ -865,7 +899,7 @@ function Welcome() {
|
|
|
865
899
|
|
|
866
900
|
// src/ui/LearnMore.tsx
|
|
867
901
|
import { Fragment as Fragment2 } from "react";
|
|
868
|
-
import { Box as Box7, Text as Text7, useInput as useInput4, useWindowSize as
|
|
902
|
+
import { Box as Box7, Text as Text7, useInput as useInput4, useWindowSize as useWindowSize5 } from "ink";
|
|
869
903
|
|
|
870
904
|
// src/ui/copy/learn-more.ts
|
|
871
905
|
var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
|
|
@@ -873,7 +907,7 @@ var accessItems = [
|
|
|
873
907
|
{
|
|
874
908
|
tag: "READ",
|
|
875
909
|
title: "Project files",
|
|
876
|
-
description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
910
|
+
description: "reads your dependency manifests (package.json, Gemfile, go.mod, pom.xml\u2026), configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
877
911
|
},
|
|
878
912
|
{
|
|
879
913
|
tag: "WRITE",
|
|
@@ -929,7 +963,7 @@ function NeverLine({
|
|
|
929
963
|
function LearnMore() {
|
|
930
964
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
931
965
|
const backToHome = useWizard((s) => s.backToHome);
|
|
932
|
-
const { columns } =
|
|
966
|
+
const { columns } = useWindowSize5();
|
|
933
967
|
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
934
968
|
useInput4((input, key) => {
|
|
935
969
|
if (key.escape) backToHome();
|
|
@@ -1142,8 +1176,8 @@ function Ribbon() {
|
|
|
1142
1176
|
import { useState as useState6 } from "react";
|
|
1143
1177
|
|
|
1144
1178
|
// src/ui/Logs.tsx
|
|
1145
|
-
import { useLayoutEffect, useRef as
|
|
1146
|
-
import { Box as Box12, Text as Text12, measureElement as
|
|
1179
|
+
import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState5 } from "react";
|
|
1180
|
+
import { Box as Box12, Text as Text12, measureElement as measureElement3, useInput as useInput5, useWindowSize as useWindowSize6 } from "ink";
|
|
1147
1181
|
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1148
1182
|
var KIND_COLOR = {
|
|
1149
1183
|
tool: COLORS.primary,
|
|
@@ -1174,15 +1208,15 @@ function formatTimestamp(ms) {
|
|
|
1174
1208
|
}
|
|
1175
1209
|
function Logs() {
|
|
1176
1210
|
const logs = useWizard((s) => s.logs);
|
|
1177
|
-
const { rows, columns } =
|
|
1178
|
-
const viewportRef =
|
|
1211
|
+
const { rows, columns } = useWindowSize6();
|
|
1212
|
+
const viewportRef = useRef3(null);
|
|
1179
1213
|
const [viewportHeight, setViewportHeight] = useState5(0);
|
|
1180
1214
|
const [viewportWidth, setViewportWidth] = useState5(0);
|
|
1181
1215
|
const [scrollOffset, setScrollOffset] = useState5(0);
|
|
1182
|
-
const prevMaxOffsetRef =
|
|
1183
|
-
|
|
1216
|
+
const prevMaxOffsetRef = useRef3(0);
|
|
1217
|
+
useLayoutEffect2(() => {
|
|
1184
1218
|
if (!viewportRef.current) return;
|
|
1185
|
-
const { width, height } =
|
|
1219
|
+
const { width, height } = measureElement3(viewportRef.current);
|
|
1186
1220
|
setViewportHeight(height);
|
|
1187
1221
|
setViewportWidth(width);
|
|
1188
1222
|
}, [rows, columns, logs.length === 0]);
|
|
@@ -1197,7 +1231,7 @@ function Logs() {
|
|
|
1197
1231
|
}
|
|
1198
1232
|
const capacityAtBottom = logs.length > viewportHeight ? Math.max(viewportHeight - 1, 0) : viewportHeight;
|
|
1199
1233
|
const maxOffset = Math.max(logs.length - capacityAtBottom, 0);
|
|
1200
|
-
|
|
1234
|
+
useLayoutEffect2(() => {
|
|
1201
1235
|
const wasAtBottom = scrollOffset >= prevMaxOffsetRef.current;
|
|
1202
1236
|
prevMaxOffsetRef.current = maxOffset;
|
|
1203
1237
|
setScrollOffset((o) => wasAtBottom ? maxOffset : Math.min(o, maxOffset));
|
|
@@ -1270,7 +1304,7 @@ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
|
1270
1304
|
function App() {
|
|
1271
1305
|
const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
|
|
1272
1306
|
const { exit } = useApp();
|
|
1273
|
-
const { columns, rows } =
|
|
1307
|
+
const { columns, rows } = useWindowSize7();
|
|
1274
1308
|
const [showLogs, setShowLogs] = useState6(false);
|
|
1275
1309
|
const finished = phase === "done" || phase === "error";
|
|
1276
1310
|
const currentStep = steps[currentStepIndex];
|
|
@@ -1324,26 +1358,28 @@ function App() {
|
|
|
1324
1358
|
width: "100%",
|
|
1325
1359
|
justifyContent: "space-between",
|
|
1326
1360
|
children: [
|
|
1327
|
-
showLogs ? (
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1361
|
+
showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
|
|
1362
|
+
/* Fill the width beside the sidebar; row layout only (would grow vertically when stacked). */
|
|
1363
|
+
/* @__PURE__ */ jsxs12(
|
|
1364
|
+
Box13,
|
|
1365
|
+
{
|
|
1366
|
+
flexDirection: "column",
|
|
1367
|
+
paddingX: 4,
|
|
1368
|
+
paddingY: 2,
|
|
1369
|
+
width: showSidebar ? 70 : "100%",
|
|
1370
|
+
flexGrow: showSidebar ? 1 : 0,
|
|
1371
|
+
children: [
|
|
1372
|
+
/* @__PURE__ */ jsx13(Notices, {}),
|
|
1373
|
+
/* @__PURE__ */ jsx13(PromptInput, {}),
|
|
1374
|
+
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
|
|
1375
|
+
phase === "error" && error && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: COLORS.status.error, children: [
|
|
1376
|
+
"\u2716 ",
|
|
1377
|
+
error
|
|
1378
|
+
] }) })
|
|
1379
|
+
]
|
|
1380
|
+
}
|
|
1381
|
+
)
|
|
1382
|
+
),
|
|
1347
1383
|
showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
|
|
1348
1384
|
]
|
|
1349
1385
|
}
|
|
@@ -1864,12 +1900,7 @@ var selectIndexStep = async (ctx) => {
|
|
|
1864
1900
|
};
|
|
1865
1901
|
|
|
1866
1902
|
// src/lib/agent.ts
|
|
1867
|
-
import {
|
|
1868
|
-
ToolLoopAgent,
|
|
1869
|
-
hasToolCall,
|
|
1870
|
-
stepCountIs,
|
|
1871
|
-
Output as Output2
|
|
1872
|
-
} from "ai";
|
|
1903
|
+
import { ToolLoopAgent, hasToolCall, Output as Output2 } from "ai";
|
|
1873
1904
|
import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
|
|
1874
1905
|
import "zod";
|
|
1875
1906
|
|
|
@@ -2124,15 +2155,647 @@ function writeCredentialsTool(ctx) {
|
|
|
2124
2155
|
// src/lib/tools/searchFiles.ts
|
|
2125
2156
|
import { tool as tool7 } from "ai";
|
|
2126
2157
|
import z10 from "zod";
|
|
2127
|
-
import { readdir as
|
|
2158
|
+
import { readdir as readdir3, readFile as readFile7 } from "node:fs/promises";
|
|
2159
|
+
import { join as join10 } from "node:path";
|
|
2160
|
+
|
|
2161
|
+
// src/lib/languages.ts
|
|
2162
|
+
import { readdir as readdir2 } from "node:fs/promises";
|
|
2163
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
2164
|
+
import { join as join9 } from "node:path";
|
|
2165
|
+
|
|
2166
|
+
// src/lib/tools/utils/packageManager.ts
|
|
2167
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
2168
|
+
import { existsSync } from "node:fs";
|
|
2128
2169
|
import { join as join8 } from "node:path";
|
|
2170
|
+
var LOCKFILES = [
|
|
2171
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
2172
|
+
["yarn.lock", "yarn"],
|
|
2173
|
+
["bun.lockb", "bun"],
|
|
2174
|
+
["bun.lock", "bun"],
|
|
2175
|
+
["package-lock.json", "npm"]
|
|
2176
|
+
];
|
|
2177
|
+
async function readPackageJson(cwd = process.cwd()) {
|
|
2178
|
+
return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
|
|
2179
|
+
}
|
|
2180
|
+
function packageManagerFrom(pkg) {
|
|
2181
|
+
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2182
|
+
}
|
|
2183
|
+
function packageManagerFromLockfile(cwd) {
|
|
2184
|
+
return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
|
|
2185
|
+
}
|
|
2186
|
+
async function detectPackageManager(cwd) {
|
|
2187
|
+
try {
|
|
2188
|
+
const pkg = await readPackageJson(cwd);
|
|
2189
|
+
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2190
|
+
} catch {
|
|
2191
|
+
}
|
|
2192
|
+
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
// src/lib/shell.ts
|
|
2196
|
+
function shellQuote(value) {
|
|
2197
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
// src/lib/languages.ts
|
|
2201
|
+
var ENTRYPOINT_TOKEN = "{entrypoint}";
|
|
2202
|
+
var INGEST_DIR = ".algolia-wizard";
|
|
2203
|
+
var PY_VENV = `${INGEST_DIR}/.venv`;
|
|
2204
|
+
var PY_VENV_PYTHON = `${PY_VENV}/bin/python`;
|
|
2205
|
+
var PY_REQUIREMENTS = `${INGEST_DIR}/requirements.txt`;
|
|
2206
|
+
var CSHARP_PROJECT = `${INGEST_DIR}/ingest/ingest.csproj`;
|
|
2207
|
+
var SWIFT_PACKAGE_DIR = `${INGEST_DIR}/Ingest`;
|
|
2208
|
+
var LANGUAGE_PROFILES = {
|
|
2209
|
+
javascript: {
|
|
2210
|
+
id: "javascript",
|
|
2211
|
+
displayName: "JavaScript/TypeScript",
|
|
2212
|
+
aliases: [
|
|
2213
|
+
"javascript",
|
|
2214
|
+
"js",
|
|
2215
|
+
"typescript",
|
|
2216
|
+
"ts",
|
|
2217
|
+
"node",
|
|
2218
|
+
"nodejs",
|
|
2219
|
+
"node.js",
|
|
2220
|
+
"bun",
|
|
2221
|
+
"deno",
|
|
2222
|
+
"ecmascript",
|
|
2223
|
+
"jsx",
|
|
2224
|
+
"tsx"
|
|
2225
|
+
],
|
|
2226
|
+
manifests: ["package.json"],
|
|
2227
|
+
// The concrete npm-family manager is resolved by detectPackageManager (it
|
|
2228
|
+
// honours the package.json `packageManager` field, which lockfiles can't
|
|
2229
|
+
// express), so one spec covers all four and `resolveToolchain` rewrites the
|
|
2230
|
+
// binary below.
|
|
2231
|
+
packageManagers: [
|
|
2232
|
+
{
|
|
2233
|
+
id: "npm",
|
|
2234
|
+
dependency: { mode: "agent-declares", file: "package.json" },
|
|
2235
|
+
installSteps: [{ argv: ["npm", "install"] }],
|
|
2236
|
+
ingest: {
|
|
2237
|
+
kind: "auto",
|
|
2238
|
+
argv: ["node", ENTRYPOINT_TOKEN],
|
|
2239
|
+
entrypointExtensions: [".mjs", ".cjs", ".js"]
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
],
|
|
2243
|
+
sdk: { packageName: "algoliasearch", versionPin: "^5", docKey: "js" },
|
|
2244
|
+
ingestEntrypointExample: `${INGEST_DIR}/ingest.mjs`,
|
|
2245
|
+
// package.json scripts are repo-defined, so they're resolved at run time by
|
|
2246
|
+
// repoVerification rather than listed here.
|
|
2247
|
+
verification: [],
|
|
2248
|
+
envReadInstruction: "Read them from `process.env`.",
|
|
2249
|
+
skipDirs: ["node_modules", "dist", "build", "coverage", ".next", "out"]
|
|
2250
|
+
},
|
|
2251
|
+
python: {
|
|
2252
|
+
id: "python",
|
|
2253
|
+
displayName: "Python",
|
|
2254
|
+
aliases: ["python", "python3", "py", "cpython"],
|
|
2255
|
+
manifests: [
|
|
2256
|
+
"pyproject.toml",
|
|
2257
|
+
"requirements.txt",
|
|
2258
|
+
"setup.py",
|
|
2259
|
+
"setup.cfg",
|
|
2260
|
+
"Pipfile"
|
|
2261
|
+
],
|
|
2262
|
+
// Deliberately one path for every Python repo: a wizard-owned venv under
|
|
2263
|
+
// .algolia-wizard. Reusing the project's uv/poetry environment would mean
|
|
2264
|
+
// mutating the developer's real dependency manifest and lockfile, and the
|
|
2265
|
+
// declare-here/install-there split is the main way ingestion silently ends
|
|
2266
|
+
// up without the SDK installed. The tradeoff: the script can import the
|
|
2267
|
+
// Algolia client and anything it declares itself, but not the project's own
|
|
2268
|
+
// packages (see the optional root-requirements step below).
|
|
2269
|
+
packageManagers: [
|
|
2270
|
+
{
|
|
2271
|
+
id: "pip-venv",
|
|
2272
|
+
dependency: { mode: "agent-declares", file: PY_REQUIREMENTS },
|
|
2273
|
+
installSteps: [
|
|
2274
|
+
{ argv: ["python3", "-m", "venv", PY_VENV] },
|
|
2275
|
+
{
|
|
2276
|
+
argv: [PY_VENV_PYTHON, "-m", "pip", "install", "-r", PY_REQUIREMENTS]
|
|
2277
|
+
},
|
|
2278
|
+
// Best-effort access to the project's own dependencies (DB drivers,
|
|
2279
|
+
// ORMs) when the repo pins them the classic way.
|
|
2280
|
+
{
|
|
2281
|
+
argv: [
|
|
2282
|
+
PY_VENV_PYTHON,
|
|
2283
|
+
"-m",
|
|
2284
|
+
"pip",
|
|
2285
|
+
"install",
|
|
2286
|
+
"-r",
|
|
2287
|
+
"requirements.txt"
|
|
2288
|
+
],
|
|
2289
|
+
requiresFile: "requirements.txt",
|
|
2290
|
+
optional: true
|
|
2291
|
+
}
|
|
2292
|
+
],
|
|
2293
|
+
ingest: {
|
|
2294
|
+
kind: "auto",
|
|
2295
|
+
argv: [PY_VENV_PYTHON, ENTRYPOINT_TOKEN],
|
|
2296
|
+
entrypointExtensions: [".py"]
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
],
|
|
2300
|
+
sdk: {
|
|
2301
|
+
packageName: "algoliasearch",
|
|
2302
|
+
versionPin: ">=4,<5",
|
|
2303
|
+
docKey: "python"
|
|
2304
|
+
},
|
|
2305
|
+
ingestEntrypointExample: `${INGEST_DIR}/ingest.py`,
|
|
2306
|
+
verification: [
|
|
2307
|
+
{
|
|
2308
|
+
label: "python compileall",
|
|
2309
|
+
argv: ["python3", "-m", "compileall", "-q", INGEST_DIR]
|
|
2310
|
+
}
|
|
2311
|
+
],
|
|
2312
|
+
envReadInstruction: "Read them from `os.environ`.",
|
|
2313
|
+
skipDirs: [
|
|
2314
|
+
"venv",
|
|
2315
|
+
"__pycache__",
|
|
2316
|
+
"site-packages",
|
|
2317
|
+
"dist",
|
|
2318
|
+
"build",
|
|
2319
|
+
"htmlcov"
|
|
2320
|
+
]
|
|
2321
|
+
},
|
|
2322
|
+
ruby: {
|
|
2323
|
+
id: "ruby",
|
|
2324
|
+
displayName: "Ruby",
|
|
2325
|
+
aliases: ["ruby", "rb", "rails", "ruby on rails", "rubyonrails"],
|
|
2326
|
+
manifests: ["Gemfile", "*.gemspec"],
|
|
2327
|
+
packageManagers: [
|
|
2328
|
+
{
|
|
2329
|
+
id: "bundler",
|
|
2330
|
+
dependency: { mode: "agent-declares", file: "Gemfile" },
|
|
2331
|
+
installSteps: [{ argv: ["bundle", "install"] }],
|
|
2332
|
+
ingest: {
|
|
2333
|
+
kind: "auto",
|
|
2334
|
+
argv: ["bundle", "exec", "ruby", ENTRYPOINT_TOKEN],
|
|
2335
|
+
entrypointExtensions: [".rb"]
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
],
|
|
2339
|
+
sdk: { packageName: "algolia", versionPin: "~> 3.0", docKey: "ruby" },
|
|
2340
|
+
ingestEntrypointExample: `${INGEST_DIR}/ingest.rb`,
|
|
2341
|
+
// Ruby has no directory-level syntax check (`ruby -c` is one file at a
|
|
2342
|
+
// time), so verification relies on the agent's own review here.
|
|
2343
|
+
verification: [],
|
|
2344
|
+
envReadInstruction: "Read them from `ENV.fetch('NAME')`.",
|
|
2345
|
+
skipDirs: ["vendor", "tmp", "log", "coverage"]
|
|
2346
|
+
},
|
|
2347
|
+
php: {
|
|
2348
|
+
id: "php",
|
|
2349
|
+
displayName: "PHP",
|
|
2350
|
+
aliases: ["php", "laravel", "symfony"],
|
|
2351
|
+
manifests: ["composer.json"],
|
|
2352
|
+
packageManagers: [
|
|
2353
|
+
{
|
|
2354
|
+
id: "composer",
|
|
2355
|
+
// `composer require` both declares and installs, and unlike editing
|
|
2356
|
+
// composer.json by hand it can't leave composer.lock out of date (which
|
|
2357
|
+
// makes a later `composer install` refuse to run).
|
|
2358
|
+
dependency: { mode: "wizard-installs" },
|
|
2359
|
+
installSteps: [
|
|
2360
|
+
{
|
|
2361
|
+
argv: [
|
|
2362
|
+
"composer",
|
|
2363
|
+
"require",
|
|
2364
|
+
"algolia/algoliasearch-client-php:^4",
|
|
2365
|
+
"--no-interaction",
|
|
2366
|
+
// Repo post-install scripts are the project's code, not ours to
|
|
2367
|
+
// trigger; Laravel's package:discover also fails in a bare tree.
|
|
2368
|
+
"--no-scripts"
|
|
2369
|
+
]
|
|
2370
|
+
}
|
|
2371
|
+
],
|
|
2372
|
+
ingest: {
|
|
2373
|
+
kind: "auto",
|
|
2374
|
+
argv: ["php", ENTRYPOINT_TOKEN],
|
|
2375
|
+
entrypointExtensions: [".php"]
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
],
|
|
2379
|
+
sdk: {
|
|
2380
|
+
packageName: "algolia/algoliasearch-client-php",
|
|
2381
|
+
versionPin: "^4",
|
|
2382
|
+
docKey: "php"
|
|
2383
|
+
},
|
|
2384
|
+
ingestEntrypointExample: `${INGEST_DIR}/ingest.php`,
|
|
2385
|
+
verification: [],
|
|
2386
|
+
envReadInstruction: "Read them from `getenv('NAME')`.",
|
|
2387
|
+
skipDirs: ["vendor", "node_modules"]
|
|
2388
|
+
},
|
|
2389
|
+
go: {
|
|
2390
|
+
id: "go",
|
|
2391
|
+
displayName: "Go",
|
|
2392
|
+
aliases: ["go", "golang"],
|
|
2393
|
+
manifests: ["go.mod"],
|
|
2394
|
+
packageManagers: [
|
|
2395
|
+
{
|
|
2396
|
+
id: "gomod",
|
|
2397
|
+
// Imports in the generated file are the declaration; `go mod tidy`
|
|
2398
|
+
// resolves and fetches them.
|
|
2399
|
+
dependency: { mode: "code-imports" },
|
|
2400
|
+
installSteps: [{ argv: ["go", "mod", "tidy"] }],
|
|
2401
|
+
ingest: {
|
|
2402
|
+
kind: "auto",
|
|
2403
|
+
argv: ["go", "run", ENTRYPOINT_TOKEN],
|
|
2404
|
+
entrypointExtensions: [".go"]
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
],
|
|
2408
|
+
sdk: {
|
|
2409
|
+
packageName: "github.com/algolia/algoliasearch-client-go/v4",
|
|
2410
|
+
versionPin: "v4",
|
|
2411
|
+
docKey: "go"
|
|
2412
|
+
},
|
|
2413
|
+
ingestEntrypointExample: `${INGEST_DIR}/ingest.go`,
|
|
2414
|
+
verification: [
|
|
2415
|
+
{ label: "go vet", argv: ["go", "vet", "./..."], requiresFile: "go.mod" }
|
|
2416
|
+
],
|
|
2417
|
+
envReadInstruction: "Read them from `os.Getenv`.",
|
|
2418
|
+
skipDirs: ["vendor", "bin"]
|
|
2419
|
+
},
|
|
2420
|
+
java: {
|
|
2421
|
+
id: "java",
|
|
2422
|
+
displayName: "Java",
|
|
2423
|
+
aliases: ["java"],
|
|
2424
|
+
manifests: ["pom.xml", "build.gradle", "build.gradle.kts"],
|
|
2425
|
+
packageManagers: [
|
|
2426
|
+
{
|
|
2427
|
+
id: "maven",
|
|
2428
|
+
detectFiles: ["pom.xml"],
|
|
2429
|
+
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2430
|
+
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2431
|
+
// The main class is a wizard constant the instructions require the agent
|
|
2432
|
+
// to use, so execution can't be redirected by agent output. Runnable only
|
|
2433
|
+
// because the install step above compiles src/main/java first — which is
|
|
2434
|
+
// why the entrypoint lives there rather than under .algolia-wizard/.
|
|
2435
|
+
ingest: {
|
|
2436
|
+
kind: "auto",
|
|
2437
|
+
argv: [
|
|
2438
|
+
"mvn",
|
|
2439
|
+
"-q",
|
|
2440
|
+
"org.codehaus.mojo:exec-maven-plugin:3.5.0:java",
|
|
2441
|
+
"-Dexec.mainClass=AlgoliaWizardIngest"
|
|
2442
|
+
],
|
|
2443
|
+
entrypointExtensions: [".java"]
|
|
2444
|
+
}
|
|
2445
|
+
},
|
|
2446
|
+
{
|
|
2447
|
+
id: "gradle",
|
|
2448
|
+
detectFiles: ["build.gradle", "build.gradle.kts"],
|
|
2449
|
+
dependency: { mode: "agent-declares", file: "build.gradle" },
|
|
2450
|
+
installSteps: [],
|
|
2451
|
+
// Auto-running means executing the repo's own ./gradlew wrapper; out of
|
|
2452
|
+
// scope for now, so the wizard writes the code and prints the command.
|
|
2453
|
+
ingest: {
|
|
2454
|
+
kind: "manual",
|
|
2455
|
+
entrypointExtensions: [".java"],
|
|
2456
|
+
runCommand: "./gradlew runAlgoliaIngest"
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
],
|
|
2460
|
+
sdk: {
|
|
2461
|
+
packageName: "com.algolia:algoliasearch",
|
|
2462
|
+
versionPin: "4.+",
|
|
2463
|
+
docKey: "java",
|
|
2464
|
+
alsoRequires: "The class must be named AlgoliaWizardIngest, in the default package (no `package` statement), with a `public static void main`."
|
|
2465
|
+
},
|
|
2466
|
+
// Not under .algolia-wizard/: Maven and Gradle only compile src/main/<lang>,
|
|
2467
|
+
// so a class outside it never makes it onto the classpath and the run command
|
|
2468
|
+
// fails with "class not found".
|
|
2469
|
+
ingestEntrypointExample: "src/main/java/AlgoliaWizardIngest.java",
|
|
2470
|
+
verification: [
|
|
2471
|
+
{
|
|
2472
|
+
label: "mvn compile",
|
|
2473
|
+
argv: ["mvn", "-q", "-DskipTests", "compile"],
|
|
2474
|
+
requiresFile: "pom.xml"
|
|
2475
|
+
}
|
|
2476
|
+
],
|
|
2477
|
+
envReadInstruction: "Read them from `System.getenv`.",
|
|
2478
|
+
skipDirs: ["target", "build", "out"]
|
|
2479
|
+
},
|
|
2480
|
+
kotlin: {
|
|
2481
|
+
id: "kotlin",
|
|
2482
|
+
displayName: "Kotlin",
|
|
2483
|
+
aliases: ["kotlin", "kt", "ktor"],
|
|
2484
|
+
manifests: ["build.gradle.kts", "build.gradle", "pom.xml"],
|
|
2485
|
+
packageManagers: [
|
|
2486
|
+
{
|
|
2487
|
+
id: "gradle",
|
|
2488
|
+
dependency: { mode: "agent-declares", file: "build.gradle.kts" },
|
|
2489
|
+
installSteps: [],
|
|
2490
|
+
ingest: {
|
|
2491
|
+
kind: "manual",
|
|
2492
|
+
entrypointExtensions: [".kt"],
|
|
2493
|
+
runCommand: "./gradlew runAlgoliaIngest"
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
],
|
|
2497
|
+
sdk: {
|
|
2498
|
+
packageName: "com.algolia:algoliasearch-client-kotlin",
|
|
2499
|
+
versionPin: "3.+",
|
|
2500
|
+
docKey: "kotlin",
|
|
2501
|
+
// The published client's commonMain ships only ktor-client-core; without an
|
|
2502
|
+
// engine the script compiles and then fails at its first request.
|
|
2503
|
+
alsoRequires: "The Kotlin client bundles no HTTP engine, so also declare one (e.g. io.ktor:ktor-client-okhttp). Name the object AlgoliaWizardIngest in the default package, with a @JvmStatic main."
|
|
2504
|
+
},
|
|
2505
|
+
ingestEntrypointExample: "src/main/kotlin/AlgoliaWizardIngest.kt",
|
|
2506
|
+
verification: [],
|
|
2507
|
+
envReadInstruction: "Read them from `System.getenv`.",
|
|
2508
|
+
skipDirs: ["build", "out"]
|
|
2509
|
+
},
|
|
2510
|
+
scala: {
|
|
2511
|
+
id: "scala",
|
|
2512
|
+
displayName: "Scala",
|
|
2513
|
+
aliases: ["scala", "sbt"],
|
|
2514
|
+
manifests: ["build.sbt", "build.sc"],
|
|
2515
|
+
packageManagers: [
|
|
2516
|
+
{
|
|
2517
|
+
id: "sbt",
|
|
2518
|
+
dependency: { mode: "agent-declares", file: "build.sbt" },
|
|
2519
|
+
installSteps: [],
|
|
2520
|
+
ingest: {
|
|
2521
|
+
kind: "manual",
|
|
2522
|
+
entrypointExtensions: [".scala"],
|
|
2523
|
+
runCommand: 'sbt "runMain AlgoliaWizardIngest"'
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
],
|
|
2527
|
+
sdk: {
|
|
2528
|
+
packageName: "com.algolia:algoliasearch-scala_2.13",
|
|
2529
|
+
versionPin: "2.+",
|
|
2530
|
+
docKey: "scala",
|
|
2531
|
+
alsoRequires: "Name the object AlgoliaWizardIngest in the default package (no `package` statement) so `runMain AlgoliaWizardIngest` resolves it."
|
|
2532
|
+
},
|
|
2533
|
+
ingestEntrypointExample: "src/main/scala/AlgoliaWizardIngest.scala",
|
|
2534
|
+
verification: [],
|
|
2535
|
+
envReadInstruction: "Read them from `sys.env`.",
|
|
2536
|
+
// `project/` holds sbt's build definition, but the name is generic enough
|
|
2537
|
+
// that some repos use it for source; scanning it is cheap, missing source
|
|
2538
|
+
// is not.
|
|
2539
|
+
skipDirs: ["target"]
|
|
2540
|
+
},
|
|
2541
|
+
csharp: {
|
|
2542
|
+
id: "csharp",
|
|
2543
|
+
displayName: "C#",
|
|
2544
|
+
aliases: ["c#", "csharp", "cs", ".net", "dotnet", "net", "asp.net"],
|
|
2545
|
+
manifests: ["*.csproj", "*.sln", "global.json"],
|
|
2546
|
+
packageManagers: [
|
|
2547
|
+
{
|
|
2548
|
+
id: "dotnet",
|
|
2549
|
+
// A self-contained project under .algolia-wizard keeps the ingest script
|
|
2550
|
+
// out of the repo's own build graph.
|
|
2551
|
+
dependency: { mode: "agent-declares", file: CSHARP_PROJECT },
|
|
2552
|
+
installSteps: [{ argv: ["dotnet", "restore", CSHARP_PROJECT] }],
|
|
2553
|
+
ingest: {
|
|
2554
|
+
kind: "auto",
|
|
2555
|
+
argv: ["dotnet", "run", "--project", ENTRYPOINT_TOKEN],
|
|
2556
|
+
entrypointExtensions: [".csproj"]
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
],
|
|
2560
|
+
sdk: {
|
|
2561
|
+
packageName: "Algolia.Search",
|
|
2562
|
+
versionPin: "7.*",
|
|
2563
|
+
docKey: "csharp"
|
|
2564
|
+
},
|
|
2565
|
+
ingestEntrypointExample: CSHARP_PROJECT,
|
|
2566
|
+
verification: [
|
|
2567
|
+
{
|
|
2568
|
+
label: "dotnet build",
|
|
2569
|
+
argv: ["dotnet", "build", CSHARP_PROJECT, "--nologo"],
|
|
2570
|
+
requiresFile: CSHARP_PROJECT
|
|
2571
|
+
}
|
|
2572
|
+
],
|
|
2573
|
+
envReadInstruction: 'Read them from `Environment.GetEnvironmentVariable("NAME")`.',
|
|
2574
|
+
// Deliberately not `packages`: modern .NET uses PackageReference, and
|
|
2575
|
+
// `packages/` is where pnpm/Lerna/Turborepo monorepos keep all their source —
|
|
2576
|
+
// skipping it would hide the entities the scan is looking for.
|
|
2577
|
+
skipDirs: ["bin", "obj"]
|
|
2578
|
+
},
|
|
2579
|
+
swift: {
|
|
2580
|
+
id: "swift",
|
|
2581
|
+
displayName: "Swift",
|
|
2582
|
+
aliases: ["swift", "swiftui", "ios", "vapor"],
|
|
2583
|
+
manifests: ["Package.swift", "*.xcodeproj", "*.xcworkspace"],
|
|
2584
|
+
packageManagers: [
|
|
2585
|
+
{
|
|
2586
|
+
id: "swiftpm",
|
|
2587
|
+
dependency: {
|
|
2588
|
+
mode: "agent-declares",
|
|
2589
|
+
file: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2590
|
+
},
|
|
2591
|
+
// `swift build` resolves and fetches; a cold build of the client is slow
|
|
2592
|
+
// (minutes), which is why the caller degrades to the manual command when
|
|
2593
|
+
// this fails.
|
|
2594
|
+
installSteps: [
|
|
2595
|
+
{
|
|
2596
|
+
argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2597
|
+
requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2598
|
+
}
|
|
2599
|
+
],
|
|
2600
|
+
ingest: {
|
|
2601
|
+
kind: "auto",
|
|
2602
|
+
argv: ["swift", "run", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2603
|
+
entrypointExtensions: [".swift"]
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
],
|
|
2607
|
+
sdk: {
|
|
2608
|
+
packageName: "algoliasearch-client-swift",
|
|
2609
|
+
// SwiftPM range syntax, not an exact version — a bare "9.0.0" in a
|
|
2610
|
+
// Package.swift dependency pins the patch.
|
|
2611
|
+
versionPin: 'from: "9.0.0"',
|
|
2612
|
+
docKey: "swift"
|
|
2613
|
+
},
|
|
2614
|
+
ingestEntrypointExample: `${SWIFT_PACKAGE_DIR}/Sources/Ingest/main.swift`,
|
|
2615
|
+
verification: [
|
|
2616
|
+
{
|
|
2617
|
+
label: "swift build",
|
|
2618
|
+
argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2619
|
+
requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2620
|
+
}
|
|
2621
|
+
],
|
|
2622
|
+
envReadInstruction: "Read them from `ProcessInfo.processInfo.environment`.",
|
|
2623
|
+
skipDirs: ["Pods", "DerivedData", "Carthage", ".build"]
|
|
2624
|
+
},
|
|
2625
|
+
dart: {
|
|
2626
|
+
id: "dart",
|
|
2627
|
+
displayName: "Dart",
|
|
2628
|
+
aliases: ["dart", "flutter"],
|
|
2629
|
+
manifests: ["pubspec.yaml"],
|
|
2630
|
+
packageManagers: [
|
|
2631
|
+
{
|
|
2632
|
+
id: "flutter-pub",
|
|
2633
|
+
detectFiles: [".metadata"],
|
|
2634
|
+
dependency: { mode: "agent-declares", file: "pubspec.yaml" },
|
|
2635
|
+
installSteps: [{ argv: ["flutter", "pub", "get"] }],
|
|
2636
|
+
ingest: {
|
|
2637
|
+
kind: "auto",
|
|
2638
|
+
argv: ["dart", "run", ENTRYPOINT_TOKEN],
|
|
2639
|
+
entrypointExtensions: [".dart"]
|
|
2640
|
+
}
|
|
2641
|
+
},
|
|
2642
|
+
{
|
|
2643
|
+
id: "pub",
|
|
2644
|
+
dependency: { mode: "agent-declares", file: "pubspec.yaml" },
|
|
2645
|
+
installSteps: [{ argv: ["dart", "pub", "get"] }],
|
|
2646
|
+
ingest: {
|
|
2647
|
+
kind: "auto",
|
|
2648
|
+
argv: ["dart", "run", ENTRYPOINT_TOKEN],
|
|
2649
|
+
entrypointExtensions: [".dart"]
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
],
|
|
2653
|
+
sdk: {
|
|
2654
|
+
packageName: "algolia_client_search",
|
|
2655
|
+
versionPin: "^1.0.0",
|
|
2656
|
+
docKey: "dart"
|
|
2657
|
+
},
|
|
2658
|
+
ingestEntrypointExample: `${INGEST_DIR}/ingest.dart`,
|
|
2659
|
+
verification: [
|
|
2660
|
+
{
|
|
2661
|
+
label: "dart analyze",
|
|
2662
|
+
argv: ["dart", "analyze", INGEST_DIR],
|
|
2663
|
+
requiresFile: "pubspec.yaml"
|
|
2664
|
+
}
|
|
2665
|
+
],
|
|
2666
|
+
envReadInstruction: "Read them from `Platform.environment`.",
|
|
2667
|
+
skipDirs: ["build"]
|
|
2668
|
+
}
|
|
2669
|
+
};
|
|
2670
|
+
var DEFAULT_LANGUAGE_ID = "javascript";
|
|
2671
|
+
function normalizeLanguageName(name) {
|
|
2672
|
+
return name.toLowerCase().trim().replace(/[\s_-]+/g, "");
|
|
2673
|
+
}
|
|
2674
|
+
var ALIAS_TO_ID = /* @__PURE__ */ new Map();
|
|
2675
|
+
for (const profile2 of Object.values(LANGUAGE_PROFILES)) {
|
|
2676
|
+
for (const alias of [profile2.id, profile2.displayName, ...profile2.aliases]) {
|
|
2677
|
+
ALIAS_TO_ID.set(normalizeLanguageName(alias), profile2.id);
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
function resolveLanguageProfile(name) {
|
|
2681
|
+
const id = ALIAS_TO_ID.get(normalizeLanguageName(name));
|
|
2682
|
+
return id ? LANGUAGE_PROFILES[id] : void 0;
|
|
2683
|
+
}
|
|
2684
|
+
var BASE_SKIP_DIRS = ["node_modules", ".git", "dist"];
|
|
2685
|
+
var ALL_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2686
|
+
...BASE_SKIP_DIRS,
|
|
2687
|
+
...Object.values(LANGUAGE_PROFILES).flatMap((p) => p.skipDirs)
|
|
2688
|
+
]);
|
|
2689
|
+
var ALLOWED_BINARIES = new Set(
|
|
2690
|
+
Object.values(LANGUAGE_PROFILES).flatMap((profile2) => [
|
|
2691
|
+
...profile2.packageManagers.flatMap((pm) => [
|
|
2692
|
+
...pm.installSteps.map((s) => s.argv[0]),
|
|
2693
|
+
...pm.ingest.kind === "auto" ? [pm.ingest.argv[0]] : []
|
|
2694
|
+
]),
|
|
2695
|
+
...profile2.verification.map((v) => v.argv[0])
|
|
2696
|
+
])
|
|
2697
|
+
);
|
|
2698
|
+
var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
|
|
2699
|
+
function isWorktreeRelativeCommand(command) {
|
|
2700
|
+
return command.includes("/");
|
|
2701
|
+
}
|
|
2702
|
+
function withCommand(argv, command) {
|
|
2703
|
+
return [command, ...argv.slice(1)];
|
|
2704
|
+
}
|
|
2705
|
+
async function manifestPresent(root, manifest, listing) {
|
|
2706
|
+
if (!manifest.startsWith("*.")) return existsSync2(join9(root, manifest));
|
|
2707
|
+
if (!listing.entries) {
|
|
2708
|
+
const entries = await readdir2(root).catch(() => []);
|
|
2709
|
+
listing.entries = Array.isArray(entries) ? entries : [];
|
|
2710
|
+
}
|
|
2711
|
+
const suffix = manifest.slice(1);
|
|
2712
|
+
return listing.entries.some((e) => e.endsWith(suffix));
|
|
2713
|
+
}
|
|
2714
|
+
async function profileManifestPresent(root, profile2, listing) {
|
|
2715
|
+
for (const manifest of profile2.manifests) {
|
|
2716
|
+
if (await manifestPresent(root, manifest, listing)) return true;
|
|
2717
|
+
}
|
|
2718
|
+
return false;
|
|
2719
|
+
}
|
|
2720
|
+
async function detectProfilesFromManifests(root) {
|
|
2721
|
+
const listing = {};
|
|
2722
|
+
const found = [];
|
|
2723
|
+
for (const profile2 of Object.values(LANGUAGE_PROFILES)) {
|
|
2724
|
+
if (await profileManifestPresent(root, profile2, listing)) found.push(profile2);
|
|
2725
|
+
}
|
|
2726
|
+
return found;
|
|
2727
|
+
}
|
|
2728
|
+
async function hasProfileManifest(root, profile2) {
|
|
2729
|
+
return profileManifestPresent(root, profile2, {});
|
|
2730
|
+
}
|
|
2731
|
+
async function resolveToolchain(root, profile2) {
|
|
2732
|
+
const matched = profile2.packageManagers.find(
|
|
2733
|
+
(pm) => [...pm.lockfiles ?? [], ...pm.detectFiles ?? []].some(
|
|
2734
|
+
(f) => existsSync2(join9(root, f))
|
|
2735
|
+
)
|
|
2736
|
+
);
|
|
2737
|
+
const packageManager = matched ?? profile2.packageManagers[0];
|
|
2738
|
+
let { installSteps, ingest } = packageManager;
|
|
2739
|
+
installSteps = installSteps.map(
|
|
2740
|
+
(step) => isWorktreeRelativeCommand(step.argv[0]) ? { ...step, argv: withCommand(step.argv, join9(root, step.argv[0])) } : step
|
|
2741
|
+
);
|
|
2742
|
+
if (ingest.kind === "auto" && isWorktreeRelativeCommand(ingest.argv[0])) {
|
|
2743
|
+
ingest = {
|
|
2744
|
+
...ingest,
|
|
2745
|
+
argv: withCommand(ingest.argv, join9(root, ingest.argv[0]))
|
|
2746
|
+
};
|
|
2747
|
+
}
|
|
2748
|
+
if (profile2.id === "javascript") {
|
|
2749
|
+
const pm = await detectPackageManager(root);
|
|
2750
|
+
if (JS_PACKAGE_MANAGERS.has(pm)) {
|
|
2751
|
+
installSteps = installSteps.map((step) => ({
|
|
2752
|
+
...step,
|
|
2753
|
+
argv: withCommand(step.argv, pm)
|
|
2754
|
+
}));
|
|
2755
|
+
if (pm === "bun" && ingest.kind === "auto") {
|
|
2756
|
+
ingest = { ...ingest, argv: withCommand(ingest.argv, "bun") };
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
return { profile: profile2, packageManager, installSteps, ingest };
|
|
2761
|
+
}
|
|
2762
|
+
function resolveIngestArgv(ingest, entrypoint) {
|
|
2763
|
+
if (ingest.kind !== "auto") {
|
|
2764
|
+
throw new Error("resolveIngestArgv called for a manual-run toolchain");
|
|
2765
|
+
}
|
|
2766
|
+
return ingest.argv.map(
|
|
2767
|
+
(part) => part === ENTRYPOINT_TOKEN ? entrypoint : part
|
|
2768
|
+
);
|
|
2769
|
+
}
|
|
2770
|
+
function describeIngestCommand(ingest, entrypoint) {
|
|
2771
|
+
if (ingest.kind !== "auto") return ingest.runCommand;
|
|
2772
|
+
return ingest.argv.map((part) => part === ENTRYPOINT_TOKEN ? shellQuote(entrypoint) : part).join(" ");
|
|
2773
|
+
}
|
|
2774
|
+
function ingestScriptDir(profile2) {
|
|
2775
|
+
const parts = profile2.ingestEntrypointExample.split("/");
|
|
2776
|
+
return parts.slice(0, -1).join("/") || ".";
|
|
2777
|
+
}
|
|
2778
|
+
function dependencyInstruction(toolchain) {
|
|
2779
|
+
const { profile: profile2, packageManager } = toolchain;
|
|
2780
|
+
const { packageName, versionPin } = profile2.sdk;
|
|
2781
|
+
const also = profile2.sdk.alsoRequires ? ` ${profile2.sdk.alsoRequires}` : "";
|
|
2782
|
+
switch (packageManager.dependency.mode) {
|
|
2783
|
+
case "wizard-installs":
|
|
2784
|
+
return `The wizard installs ${packageName} ${versionPin} in the worktree after you finish \u2014 import it directly and do not edit dependency manifests for it.${also}`;
|
|
2785
|
+
case "code-imports":
|
|
2786
|
+
return `Import ${packageName} in the script; the wizard resolves and fetches it in the worktree after you finish. Do not edit dependency manifests by hand.${also}`;
|
|
2787
|
+
case "agent-declares":
|
|
2788
|
+
return `Declare ${packageName} ${versionPin} in "${packageManager.dependency.file}" (create the file if needed), plus any other dependency your script imports; the wizard installs them in the worktree after you finish.${also}`;
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
// src/lib/tools/searchFiles.ts
|
|
2129
2793
|
var MAX_QUERY_LENGTH = 1e3;
|
|
2130
2794
|
async function walkFiles(dir) {
|
|
2131
|
-
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
2132
2795
|
const out = [];
|
|
2133
|
-
for (const e of await
|
|
2134
|
-
if (e.name.startsWith(".") ||
|
|
2135
|
-
const full =
|
|
2796
|
+
for (const e of await readdir3(dir, { withFileTypes: true })) {
|
|
2797
|
+
if (e.name.startsWith(".") || ALL_SKIP_DIRS.has(e.name)) continue;
|
|
2798
|
+
const full = join10(dir, e.name);
|
|
2136
2799
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2137
2800
|
else if (e.isFile()) out.push(full);
|
|
2138
2801
|
}
|
|
@@ -2165,7 +2828,7 @@ function searchFilesTool(ctx) {
|
|
|
2165
2828
|
for (const file of await walkFiles(resolved.target)) {
|
|
2166
2829
|
let content;
|
|
2167
2830
|
try {
|
|
2168
|
-
content = await
|
|
2831
|
+
content = await readFile7(file, "utf8");
|
|
2169
2832
|
} catch {
|
|
2170
2833
|
continue;
|
|
2171
2834
|
}
|
|
@@ -2189,6 +2852,10 @@ function searchFilesTool(ctx) {
|
|
|
2189
2852
|
import { tool as tool8 } from "ai";
|
|
2190
2853
|
import z11 from "zod";
|
|
2191
2854
|
|
|
2855
|
+
// src/lib/tools/repoVerification.ts
|
|
2856
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
2857
|
+
import { join as join11 } from "node:path";
|
|
2858
|
+
|
|
2192
2859
|
// src/lib/tools/utils/runCommand.ts
|
|
2193
2860
|
import { spawn as spawn2 } from "node:child_process";
|
|
2194
2861
|
function runCommand(command, args, cwd) {
|
|
@@ -2208,69 +2875,89 @@ function runCommand(command, args, cwd) {
|
|
|
2208
2875
|
});
|
|
2209
2876
|
}
|
|
2210
2877
|
|
|
2211
|
-
// src/lib/tools/utils/packageManager.ts
|
|
2212
|
-
import { readFile as readFile7 } from "node:fs/promises";
|
|
2213
|
-
import { existsSync } from "node:fs";
|
|
2214
|
-
import { join as join9 } from "node:path";
|
|
2215
|
-
var LOCKFILES = [
|
|
2216
|
-
["pnpm-lock.yaml", "pnpm"],
|
|
2217
|
-
["yarn.lock", "yarn"],
|
|
2218
|
-
["bun.lockb", "bun"],
|
|
2219
|
-
["bun.lock", "bun"],
|
|
2220
|
-
["package-lock.json", "npm"]
|
|
2221
|
-
];
|
|
2222
|
-
async function readPackageJson(cwd = process.cwd()) {
|
|
2223
|
-
return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
|
|
2224
|
-
}
|
|
2225
|
-
function packageManagerFrom(pkg) {
|
|
2226
|
-
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2227
|
-
}
|
|
2228
|
-
function packageManagerFromLockfile(cwd) {
|
|
2229
|
-
return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
|
|
2230
|
-
}
|
|
2231
|
-
async function detectPackageManager(cwd) {
|
|
2232
|
-
try {
|
|
2233
|
-
const pkg = await readPackageJson(cwd);
|
|
2234
|
-
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2235
|
-
} catch {
|
|
2236
|
-
}
|
|
2237
|
-
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2238
|
-
}
|
|
2239
|
-
|
|
2240
2878
|
// src/lib/tools/repoVerification.ts
|
|
2241
2879
|
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
2242
|
-
async function
|
|
2880
|
+
async function runCheck(command, binary, args) {
|
|
2881
|
+
const { code, output } = await runCommand(binary, args);
|
|
2882
|
+
return { command, exitCode: code, ok: code === 0, output: output.trim() };
|
|
2883
|
+
}
|
|
2884
|
+
async function javascriptChecks() {
|
|
2243
2885
|
let pkg;
|
|
2244
2886
|
try {
|
|
2245
2887
|
pkg = await readPackageJson();
|
|
2246
2888
|
} catch (err) {
|
|
2247
|
-
|
|
2248
|
-
|
|
2889
|
+
return {
|
|
2890
|
+
limitation: `Could not read package.json to detect verification conventions: ${err.message}`
|
|
2891
|
+
};
|
|
2249
2892
|
}
|
|
2250
2893
|
const scripts = pkg.scripts ?? {};
|
|
2251
2894
|
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
2252
2895
|
if (present.length === 0) {
|
|
2253
|
-
|
|
2254
|
-
|
|
2896
|
+
return {
|
|
2897
|
+
limitation: `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`
|
|
2898
|
+
};
|
|
2255
2899
|
}
|
|
2256
2900
|
const pm = await detectPackageManager(process.cwd());
|
|
2257
2901
|
const checks = [];
|
|
2258
2902
|
for (const script of present) {
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2903
|
+
checks.push(
|
|
2904
|
+
await runCheck(`${pm} run ${script}`, pm, ["run", script])
|
|
2905
|
+
);
|
|
2262
2906
|
}
|
|
2263
|
-
return {
|
|
2907
|
+
return { checks };
|
|
2908
|
+
}
|
|
2909
|
+
async function runRepoVerificationCheck(languages = [DEFAULT_LANGUAGE_ID]) {
|
|
2910
|
+
const ids = [...new Set(languages)];
|
|
2911
|
+
if (ids.length === 0) ids.push(DEFAULT_LANGUAGE_ID);
|
|
2912
|
+
const checks = [];
|
|
2913
|
+
const limitations = [];
|
|
2914
|
+
for (const id of ids) {
|
|
2915
|
+
if (id === DEFAULT_LANGUAGE_ID) {
|
|
2916
|
+
const result = await javascriptChecks();
|
|
2917
|
+
if ("checks" in result) checks.push(...result.checks);
|
|
2918
|
+
else limitations.push(result.limitation);
|
|
2919
|
+
continue;
|
|
2920
|
+
}
|
|
2921
|
+
const profile2 = LANGUAGE_PROFILES[id];
|
|
2922
|
+
const runnable = profile2.verification.filter(
|
|
2923
|
+
(spec) => !spec.requiresFile || existsSync3(join11(process.cwd(), spec.requiresFile))
|
|
2924
|
+
);
|
|
2925
|
+
if (runnable.length === 0) {
|
|
2926
|
+
limitations.push(
|
|
2927
|
+
`No mechanical verification available for ${profile2.displayName} in this repo.`
|
|
2928
|
+
);
|
|
2929
|
+
continue;
|
|
2930
|
+
}
|
|
2931
|
+
for (const spec of runnable) {
|
|
2932
|
+
checks.push(
|
|
2933
|
+
await runCheck(spec.argv.join(" "), spec.argv[0], [
|
|
2934
|
+
...spec.argv.slice(1)
|
|
2935
|
+
])
|
|
2936
|
+
);
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
if (checks.length === 0) {
|
|
2940
|
+
return {
|
|
2941
|
+
ok: false,
|
|
2942
|
+
checks: [],
|
|
2943
|
+
limitation: limitations.join(" ") || "No verification checks available."
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2946
|
+
return {
|
|
2947
|
+
ok: checks.every((c) => c.ok),
|
|
2948
|
+
checks,
|
|
2949
|
+
...limitations.length ? { limitation: limitations.join(" ") } : {}
|
|
2950
|
+
};
|
|
2264
2951
|
}
|
|
2265
2952
|
|
|
2266
2953
|
// src/lib/tools/verifyImplementation.ts
|
|
2267
|
-
function verifyImplementationTool() {
|
|
2954
|
+
function verifyImplementationTool(ctx) {
|
|
2268
2955
|
return tool8({
|
|
2269
|
-
description: "Run the repo's mechanical verification
|
|
2956
|
+
description: "Run the repo's mechanical verification checks for generated implementation changes. Uses the conventions of the repo's languages (package.json lint/typecheck/check scripts for JavaScript, the equivalent compile/analyze command elsewhere) and returns structured pass/fail evidence for the verifier to interpret.",
|
|
2270
2957
|
inputSchema: z11.object(),
|
|
2271
2958
|
execute: async () => {
|
|
2272
|
-
logger.info("called verifyImplementation tool");
|
|
2273
|
-
return runRepoVerificationCheck();
|
|
2959
|
+
logger.info({ languages: ctx.languages }, "called verifyImplementation tool");
|
|
2960
|
+
return runRepoVerificationCheck(ctx.languages);
|
|
2274
2961
|
}
|
|
2275
2962
|
});
|
|
2276
2963
|
}
|
|
@@ -2396,12 +3083,13 @@ var DEFAULT_TOOL_LIMITS = {
|
|
|
2396
3083
|
read: 20,
|
|
2397
3084
|
match: 100
|
|
2398
3085
|
};
|
|
2399
|
-
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
|
|
3086
|
+
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd(), languages = [DEFAULT_LANGUAGE_ID]) {
|
|
2400
3087
|
return {
|
|
2401
3088
|
root: cwd,
|
|
2402
3089
|
cwd,
|
|
2403
3090
|
limits,
|
|
2404
|
-
counts: { list: 0, search: 0, read: 0 }
|
|
3091
|
+
counts: { list: 0, search: 0, read: 0 },
|
|
3092
|
+
languages: languages.length ? languages : [DEFAULT_LANGUAGE_ID]
|
|
2405
3093
|
};
|
|
2406
3094
|
}
|
|
2407
3095
|
|
|
@@ -2438,7 +3126,7 @@ function createTools(ctx, { output, tools }) {
|
|
|
2438
3126
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
2439
3127
|
verifyImplementation: withLogging(
|
|
2440
3128
|
"verifyImplementation",
|
|
2441
|
-
verifyImplementationTool()
|
|
3129
|
+
verifyImplementationTool(ctx)
|
|
2442
3130
|
),
|
|
2443
3131
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
2444
3132
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
@@ -2462,41 +3150,9 @@ var MODEL_BY_SIZE = {
|
|
|
2462
3150
|
medium: "claude-sonnet-4-6",
|
|
2463
3151
|
large: "claude-opus-4-8"
|
|
2464
3152
|
};
|
|
2465
|
-
var STEP_TIMEOUT_MS = 15e4;
|
|
2466
|
-
var CHUNK_TIMEOUT_MS = 45e3;
|
|
2467
|
-
var MAX_STEPS = 100;
|
|
2468
|
-
function withRollingCacheBreakpoint(messages) {
|
|
2469
|
-
const last = messages.at(-1);
|
|
2470
|
-
if (!last) return messages;
|
|
2471
|
-
return [
|
|
2472
|
-
...messages.slice(0, -1),
|
|
2473
|
-
{
|
|
2474
|
-
...last,
|
|
2475
|
-
providerOptions: {
|
|
2476
|
-
...last.providerOptions,
|
|
2477
|
-
anthropic: {
|
|
2478
|
-
...last.providerOptions?.anthropic,
|
|
2479
|
-
cacheControl: { type: "ephemeral" }
|
|
2480
|
-
}
|
|
2481
|
-
}
|
|
2482
|
-
}
|
|
2483
|
-
];
|
|
2484
|
-
}
|
|
2485
|
-
function asAgentError(name, err) {
|
|
2486
|
-
const aborted = err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
|
|
2487
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2488
|
-
return new Error(
|
|
2489
|
-
aborted ? `${name} agent stalled \u2014 no response for ${STEP_TIMEOUT_MS / 1e3}s, or the stream died mid-response` : `${name} agent failed: ${message}`,
|
|
2490
|
-
{ cause: err }
|
|
2491
|
-
);
|
|
2492
|
-
}
|
|
2493
3153
|
async function runAgent(req) {
|
|
2494
3154
|
const start = Date.now();
|
|
2495
|
-
|
|
2496
|
-
logger.info(
|
|
2497
|
-
{ agent: name, startedAt: new Date(start).toISOString() },
|
|
2498
|
-
"runAgent started"
|
|
2499
|
-
);
|
|
3155
|
+
logger.info({ startedAt: new Date(start).toISOString() }, "runAgent started");
|
|
2500
3156
|
const token = getAuthToken();
|
|
2501
3157
|
if (!token) {
|
|
2502
3158
|
throw new Error("Not authenticated: no user token available");
|
|
@@ -2506,7 +3162,11 @@ async function runAgent(req) {
|
|
|
2506
3162
|
baseURL: PROXY_BASE_URL,
|
|
2507
3163
|
fetch: proxyFetch
|
|
2508
3164
|
});
|
|
2509
|
-
const toolContext = createToolContext(
|
|
3165
|
+
const toolContext = createToolContext(
|
|
3166
|
+
void 0,
|
|
3167
|
+
void 0,
|
|
3168
|
+
req.languages
|
|
3169
|
+
);
|
|
2510
3170
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
2511
3171
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
2512
3172
|
const instructions = [
|
|
@@ -2516,16 +3176,11 @@ async function runAgent(req) {
|
|
|
2516
3176
|
] : [],
|
|
2517
3177
|
"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."
|
|
2518
3178
|
];
|
|
2519
|
-
let streamError;
|
|
2520
|
-
const logStreamErrors = {
|
|
2521
|
-
onError: ({ error }) => logger.error({ agent: name, err: error }, "agent stream error")
|
|
2522
|
-
};
|
|
2523
3179
|
const agent = new ToolLoopAgent({
|
|
2524
|
-
...logStreamErrors,
|
|
2525
3180
|
model: anthropic2(MODEL_BY_SIZE[req.modelSize ?? "medium"]),
|
|
2526
|
-
// Cache the
|
|
2527
|
-
//
|
|
2528
|
-
//
|
|
3181
|
+
// Cache tools + system on the last system block. Tools render before
|
|
3182
|
+
// system, so one breakpoint here caches both, reused on every loop turn
|
|
3183
|
+
// after the first.
|
|
2529
3184
|
instructions: instructions.map((i, idx, arr) => {
|
|
2530
3185
|
return {
|
|
2531
3186
|
role: "system",
|
|
@@ -2543,70 +3198,51 @@ async function runAgent(req) {
|
|
|
2543
3198
|
tools: req.tools
|
|
2544
3199
|
}),
|
|
2545
3200
|
toolChoice: "required",
|
|
2546
|
-
stopWhen: [hasToolCall("reportStatus")
|
|
2547
|
-
prepareStep: ({ messages }) => ({
|
|
2548
|
-
messages: withRollingCacheBreakpoint(messages)
|
|
2549
|
-
})
|
|
3201
|
+
stopWhen: [hasToolCall("reportStatus")]
|
|
2550
3202
|
});
|
|
2551
3203
|
const stream = await agent.stream({
|
|
2552
|
-
prompt: "Follow system instructions"
|
|
2553
|
-
timeout: { stepMs: STEP_TIMEOUT_MS, chunkMs: CHUNK_TIMEOUT_MS }
|
|
3204
|
+
prompt: "Follow system instructions"
|
|
2554
3205
|
});
|
|
2555
3206
|
let chunks = [];
|
|
2556
3207
|
const chunkLimit = 5;
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
continue;
|
|
2562
|
-
}
|
|
2563
|
-
if (part.type !== "text-delta") continue;
|
|
2564
|
-
if (chunks.length < chunkLimit) {
|
|
2565
|
-
chunks.push(part.text);
|
|
2566
|
-
continue;
|
|
2567
|
-
}
|
|
2568
|
-
chunks.push(part.text);
|
|
2569
|
-
logger.debug(chunks.join(""));
|
|
2570
|
-
chunks = [];
|
|
3208
|
+
for await (const chunk of stream.textStream) {
|
|
3209
|
+
if (chunks.length < chunkLimit) {
|
|
3210
|
+
chunks.push(chunk);
|
|
3211
|
+
continue;
|
|
2571
3212
|
}
|
|
2572
|
-
|
|
2573
|
-
|
|
3213
|
+
chunks.push(chunk);
|
|
3214
|
+
logger.debug(chunks.join(""));
|
|
3215
|
+
chunks = [];
|
|
2574
3216
|
}
|
|
2575
3217
|
if (chunks.length) {
|
|
2576
3218
|
logger.debug(chunks.join(""));
|
|
2577
3219
|
}
|
|
2578
3220
|
const end = Date.now();
|
|
2579
|
-
|
|
2580
|
-
let toolResults;
|
|
2581
|
-
try {
|
|
2582
|
-
usage = await stream.totalUsage;
|
|
2583
|
-
toolResults = await stream.toolResults;
|
|
2584
|
-
} catch (err) {
|
|
2585
|
-
throw asAgentError(name, streamError ?? err);
|
|
2586
|
-
}
|
|
3221
|
+
const usage = await stream.totalUsage;
|
|
2587
3222
|
logger.info(
|
|
2588
3223
|
{
|
|
2589
|
-
agent: name,
|
|
2590
3224
|
finishedAt: new Date(end).toISOString(),
|
|
2591
3225
|
durationMs: end - start,
|
|
3226
|
+
// cachedInputTokens > 0 confirms prompt caching engaged. If it stays 0
|
|
3227
|
+
// across turns, the tools+system prefix is under the model's min
|
|
3228
|
+
// cacheable size (2048 tokens for sonnet-4-6) and caching is a no-op.
|
|
2592
3229
|
usage
|
|
2593
3230
|
},
|
|
2594
3231
|
"runAgent finished"
|
|
2595
3232
|
);
|
|
2596
3233
|
logger.info(
|
|
2597
|
-
{
|
|
3234
|
+
{ counts: toolContext.counts, limits: toolContext.limits },
|
|
2598
3235
|
"tool usage"
|
|
2599
3236
|
);
|
|
3237
|
+
const toolResults = await stream.toolResults;
|
|
2600
3238
|
const report = [...toolResults].reverse().find((r) => r.toolName === "reportStatus");
|
|
2601
3239
|
if (!report) {
|
|
2602
|
-
throw new Error(
|
|
2603
|
-
cause: streamError
|
|
2604
|
-
});
|
|
3240
|
+
throw new Error("Agent finished without calling reportStatus");
|
|
2605
3241
|
}
|
|
2606
3242
|
const result = report.output;
|
|
2607
3243
|
if (result.status !== "success") {
|
|
2608
3244
|
throw new Error(
|
|
2609
|
-
|
|
3245
|
+
`Agent reported failure: ${result.reason ?? "no reason given"}`
|
|
2610
3246
|
);
|
|
2611
3247
|
}
|
|
2612
3248
|
return result.output;
|
|
@@ -2621,8 +3257,11 @@ var detectLanguageSchema = z16.object({
|
|
|
2621
3257
|
var detectLanguage = () => runAgent({
|
|
2622
3258
|
instructions: [
|
|
2623
3259
|
"Analyze the codebase and determine the programming languages and frameworks used",
|
|
2624
|
-
"
|
|
3260
|
+
"Start from the dependency manifests: package.json, pyproject.toml, requirements.txt, Gemfile, composer.json, go.mod, pom.xml, build.gradle(.kts), *.csproj, build.sbt, Package.swift, pubspec.yaml.",
|
|
3261
|
+
"List the language that owns the backend/data code first \u2014 that is the one an ingestion script will be written in.",
|
|
3262
|
+
"If a superset language is found, exclude the subset language. TS-over-JS. Kotlin-over-Java when Kotlin is primary.",
|
|
2625
3263
|
"If a meta-framework is used, exclude the framework. Next-over-React.",
|
|
3264
|
+
"Frameworks include backend and server-rendering frameworks (e.g. Rails, Django, Laravel, Symfony, Spring Boot, ASP.NET Core, Flask, Gin, Ktor) as well as frontend ones (React, Vue, Angular, Svelte) and mobile ones (Flutter, SwiftUI).",
|
|
2626
3265
|
"Return the exact version",
|
|
2627
3266
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
2628
3267
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
@@ -2630,8 +3269,7 @@ var detectLanguage = () => runAgent({
|
|
|
2630
3269
|
],
|
|
2631
3270
|
tools: ["listFiles", "changeDirectory", "readFile", "searchFiles"],
|
|
2632
3271
|
outputSchema: detectLanguageSchema,
|
|
2633
|
-
modelSize: "small"
|
|
2634
|
-
name: "detect-language"
|
|
3272
|
+
modelSize: "small"
|
|
2635
3273
|
});
|
|
2636
3274
|
|
|
2637
3275
|
// src/actions/analyzeCodebase.ts
|
|
@@ -2672,6 +3310,7 @@ var MODE_CONFIG = {
|
|
|
2672
3310
|
"Analyze the codebase to find the data entities (models) that should be ingested into Algolia.",
|
|
2673
3311
|
"For each entity, return its name, the file path(s) where it is defined, and its indexable attribute keys (the fields a user would search or filter on).",
|
|
2674
3312
|
"Inspect the source of each entity to extract real field names for attributes \u2014 do not guess or leave attributes empty.",
|
|
3313
|
+
"Entities live wherever the stack keeps them: TypeScript interfaces or a Prisma schema, Django models.py, Rails app/models, Laravel Eloquent models, JPA @Entity classes, Go structs, C# entity classes, Pydantic models.",
|
|
2675
3314
|
"Prefer domain models (e.g. Document, Product, User) over framework or infrastructure types.",
|
|
2676
3315
|
"Use as few tools as possible, but do not guess. If you cannot find any entities, return an empty array.",
|
|
2677
3316
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
@@ -2683,8 +3322,9 @@ var MODE_CONFIG = {
|
|
|
2683
3322
|
instructions: [
|
|
2684
3323
|
"Analyze the codebase to determine the single best location to add search UI functionality.",
|
|
2685
3324
|
"Prefer a shared, always-rendered layout location (e.g. a header or navigation component) so search is reachable across the app.",
|
|
2686
|
-
"
|
|
2687
|
-
|
|
3325
|
+
"It may be a client-side component or a server-rendered template \u2014 return whichever the project actually renders its UI from (e.g. /layouts/header.tsx, app/views/layouts/application.html.erb, templates/base.html, resources/views/layouts/app.blade.php, templates/base.html.twig).",
|
|
3326
|
+
"Return one file path as searchImplementationAnalysis.",
|
|
3327
|
+
'Use as few tools as possible, but do not guess. If the project renders no UI at all (an API-only service), say "unknown".',
|
|
2688
3328
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
2689
3329
|
"When done, call reportStatus"
|
|
2690
3330
|
],
|
|
@@ -2693,8 +3333,8 @@ var MODE_CONFIG = {
|
|
|
2693
3333
|
verification: {
|
|
2694
3334
|
instructions: [
|
|
2695
3335
|
"Analyze the codebase to determine which code-quality tools are available to validate changes.",
|
|
2696
|
-
"Look at package.json scripts,
|
|
2697
|
-
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"].',
|
|
3336
|
+
"Look at the dependency manifest and config files for the project's languages: package.json scripts with tsconfig/eslint/prettier, pyproject.toml or setup.cfg (ruff, mypy, black), Gemfile with .rubocop.yml, composer.json scripts (phpstan, pint), go.mod with a golangci-lint config, Maven/Gradle verification tasks, .NET analyzers, analysis_options.yaml.",
|
|
3337
|
+
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"] or ["ruff", "mypy"].',
|
|
2698
3338
|
"Use as few tools as possible, but do not guess. If you cannot find any, return an empty array.",
|
|
2699
3339
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
2700
3340
|
"When done, call reportStatus"
|
|
@@ -2707,8 +3347,7 @@ function runMode(mode, extraInstructions = []) {
|
|
|
2707
3347
|
return runAgent({
|
|
2708
3348
|
instructions: [...instructions, ...extraInstructions],
|
|
2709
3349
|
tools: READONLY_TOOLS,
|
|
2710
|
-
outputSchema
|
|
2711
|
-
name: `analyze:${mode}`
|
|
3350
|
+
outputSchema
|
|
2712
3351
|
});
|
|
2713
3352
|
}
|
|
2714
3353
|
async function runAnalysis(mode, extraInstructions = []) {
|
|
@@ -2722,7 +3361,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
2722
3361
|
// package.json
|
|
2723
3362
|
var package_default = {
|
|
2724
3363
|
name: "@algolia/wizard",
|
|
2725
|
-
version: "0.
|
|
3364
|
+
version: "0.6.0-rc.51.22",
|
|
2726
3365
|
description: "Magically implement Algolia functionality in your codebase",
|
|
2727
3366
|
type: "module",
|
|
2728
3367
|
engines: {
|
|
@@ -2825,82 +3464,175 @@ function parseEntries(raw) {
|
|
|
2825
3464
|
return raw.split(",").map(clean).filter(Boolean).slice(0, MAX_ENTRIES).map((name) => ({ name, version: "unknown" }));
|
|
2826
3465
|
}
|
|
2827
3466
|
var summarize = (entries) => entries.length ? entries.map((e) => e.name).join(", ") : "none";
|
|
2828
|
-
|
|
3467
|
+
|
|
3468
|
+
// src/actions/confirmLanguage.ts
|
|
3469
|
+
import z19 from "zod";
|
|
3470
|
+
var confirmLanguageSchema = z19.object({
|
|
3471
|
+
languages: detectLanguageSchema.shape.languages
|
|
3472
|
+
});
|
|
3473
|
+
var OTHER_OPTION = "Other";
|
|
3474
|
+
var CURATED_LANGUAGES = Object.values(LANGUAGE_PROFILES).map(
|
|
3475
|
+
(profile2) => profile2.displayName
|
|
3476
|
+
);
|
|
3477
|
+
function isSameLanguage(a, b) {
|
|
3478
|
+
const x = resolveLanguageProfile(a);
|
|
3479
|
+
const y = resolveLanguageProfile(b);
|
|
3480
|
+
if (x && y) return x.id === y.id;
|
|
3481
|
+
if (x || y) return false;
|
|
3482
|
+
const fold = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3483
|
+
return fold(a) !== "" && fold(a) === fold(b);
|
|
3484
|
+
}
|
|
3485
|
+
function confirmed(languages) {
|
|
3486
|
+
track("AI Wizard Language Confirmed", { languages });
|
|
3487
|
+
return { languages };
|
|
3488
|
+
}
|
|
3489
|
+
async function askOtherLanguage(ctx) {
|
|
3490
|
+
let prompt = "enter the language for your ingestion script";
|
|
2829
3491
|
for (; ; ) {
|
|
2830
3492
|
const answer = await ctx.requestUserInput({
|
|
2831
3493
|
prompt,
|
|
2832
3494
|
promptType: "textInput",
|
|
2833
|
-
options: []
|
|
2834
|
-
helpText: 'Comma-separated, e.g. "TypeScript, Node".'
|
|
3495
|
+
options: []
|
|
2835
3496
|
});
|
|
2836
3497
|
if (typeof answer !== "string") {
|
|
2837
|
-
throw new Error("
|
|
3498
|
+
throw new Error("confirmLanguage received an unexpected non-text result");
|
|
2838
3499
|
}
|
|
2839
|
-
const
|
|
2840
|
-
if (
|
|
2841
|
-
prompt = "
|
|
3500
|
+
const name = parseEntries(answer)[0]?.name;
|
|
3501
|
+
if (name) return name;
|
|
3502
|
+
prompt = "please enter a language name:";
|
|
2842
3503
|
}
|
|
2843
3504
|
}
|
|
2844
|
-
|
|
2845
|
-
// src/actions/confirmLanguage.ts
|
|
2846
|
-
import z19 from "zod";
|
|
2847
|
-
var confirmLanguageSchema = z19.object({
|
|
2848
|
-
languages: detectLanguageSchema.shape.languages
|
|
2849
|
-
});
|
|
2850
3505
|
async function confirmLanguage(ctx) {
|
|
2851
3506
|
const detected = ctx.getStepOutput("project-scan");
|
|
2852
|
-
const
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
3507
|
+
const detectedLanguages = detected.languages ?? [];
|
|
3508
|
+
const others = (chosen) => detectedLanguages.filter((l) => !isSameLanguage(l.name, chosen));
|
|
3509
|
+
const primary = detectedLanguages[0];
|
|
3510
|
+
if (primary) {
|
|
3511
|
+
const accepted = await ctx.requestUserInput({
|
|
3512
|
+
prompt: `Write the ingestion script in ${primary.name}?`,
|
|
3513
|
+
promptType: "acceptReject",
|
|
3514
|
+
options: [`Confirm ${primary.name}`, "Use a different language"],
|
|
3515
|
+
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0],
|
|
3516
|
+
messages: detectedLanguages.length > 1 ? [`Detected: ${summarize(detectedLanguages)}`] : []
|
|
3517
|
+
});
|
|
3518
|
+
if (accepted === true) return confirmed(detectedLanguages);
|
|
3519
|
+
}
|
|
3520
|
+
const options = [...CURATED_LANGUAGES];
|
|
3521
|
+
for (const language of detectedLanguages) {
|
|
3522
|
+
if (!options.some((o) => isSameLanguage(o, language.name))) {
|
|
3523
|
+
options.push(language.name);
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
options.push(OTHER_OPTION);
|
|
3527
|
+
const detectedFor = (option) => detectedLanguages.find((l) => isSameLanguage(option, l.name));
|
|
3528
|
+
const secondary = options.map(
|
|
3529
|
+
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
3530
|
+
);
|
|
3531
|
+
const defaultSelectedIndex = Math.max(
|
|
3532
|
+
options.findIndex((o) => detectedFor(o)),
|
|
3533
|
+
0
|
|
3534
|
+
);
|
|
3535
|
+
const selection = await ctx.requestUserInput({
|
|
3536
|
+
prompt: "select the language for your ingestion script",
|
|
3537
|
+
promptType: "multipleChoice",
|
|
3538
|
+
options,
|
|
3539
|
+
secondary,
|
|
3540
|
+
defaultSelectedIndex
|
|
2863
3541
|
});
|
|
2864
|
-
|
|
3542
|
+
if (typeof selection !== "string") {
|
|
3543
|
+
throw new Error("confirmLanguage received an unexpected non-text result");
|
|
3544
|
+
}
|
|
3545
|
+
const name = selection === OTHER_OPTION ? await askOtherLanguage(ctx) : selection;
|
|
3546
|
+
const version = detectedFor(name)?.version ?? "unknown";
|
|
3547
|
+
return confirmed([{ name, version }, ...others(name)]);
|
|
2865
3548
|
}
|
|
2866
3549
|
|
|
2867
3550
|
// src/actions/confirmFramework.ts
|
|
2868
3551
|
import z20 from "zod";
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
var
|
|
2873
|
-
|
|
2874
|
-
"
|
|
2875
|
-
"
|
|
2876
|
-
"
|
|
2877
|
-
"
|
|
2878
|
-
|
|
3552
|
+
|
|
3553
|
+
// src/lib/frameworks.ts
|
|
3554
|
+
var BACKEND_ONLY_FRAMEWORK = "Backend only / API";
|
|
3555
|
+
var FRAMEWORKS = [
|
|
3556
|
+
// Frontend — InstantSearch component flavors.
|
|
3557
|
+
{ name: "Next.js", strategy: "react", aliases: ["next", "nextjs"] },
|
|
3558
|
+
{ name: "React", strategy: "react", aliases: ["reactjs"] },
|
|
3559
|
+
{ name: "Vue", strategy: "vue", aliases: ["vuejs", "nuxt", "nuxtjs"] },
|
|
3560
|
+
{ name: "Angular", strategy: "angular", aliases: ["angularjs"] },
|
|
3561
|
+
// No Svelte InstantSearch flavor exists, so it uses InstantSearch.js.
|
|
3562
|
+
{ name: "Svelte", strategy: "js", aliases: ["sveltekit"] },
|
|
3563
|
+
{
|
|
3564
|
+
name: "Vanilla JS",
|
|
3565
|
+
strategy: "js",
|
|
3566
|
+
aliases: ["vanilla", "javascript", "js", "astro", "vite"]
|
|
3567
|
+
},
|
|
3568
|
+
// Backend — Algolia's official framework integrations. Server-rendered
|
|
3569
|
+
// templates get InstantSearch.js from a CDN.
|
|
3570
|
+
{
|
|
3571
|
+
name: "Rails",
|
|
3572
|
+
strategy: "cdn-template",
|
|
3573
|
+
aliases: ["rubyonrails", "ruby on rails", "erb"]
|
|
3574
|
+
},
|
|
3575
|
+
{ name: "Django", strategy: "cdn-template", aliases: ["jinja", "jinja2"] },
|
|
3576
|
+
{ name: "Laravel", strategy: "cdn-template", aliases: ["blade"] },
|
|
3577
|
+
{ name: "Symfony", strategy: "cdn-template", aliases: ["twig"] },
|
|
3578
|
+
// Mobile — Algolia ships InstantSearch iOS/Android and Dart clients, but the
|
|
3579
|
+
// wizard can't scaffold a native UI, so it points at the docs instead.
|
|
3580
|
+
{ name: "Flutter", strategy: "none", aliases: [] },
|
|
3581
|
+
{ name: "iOS", strategy: "none", aliases: ["swiftui", "uikit"] },
|
|
3582
|
+
{ name: "Android", strategy: "none", aliases: ["jetpack compose", "compose"] },
|
|
3583
|
+
{ name: BACKEND_ONLY_FRAMEWORK, strategy: "cdn-template", aliases: [] }
|
|
2879
3584
|
];
|
|
2880
|
-
var
|
|
3585
|
+
var CURATED_FRAMEWORKS = FRAMEWORKS.map(
|
|
3586
|
+
(f) => f.name
|
|
3587
|
+
);
|
|
2881
3588
|
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2882
|
-
var
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
javascript: "vanillajs",
|
|
2896
|
-
js: "vanillajs"
|
|
2897
|
-
};
|
|
2898
|
-
var isSameFramework = (a, b) => {
|
|
2899
|
-
const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
|
|
2900
|
-
const y = FRAMEWORK_ALIASES[normalize(b)] ?? normalize(b);
|
|
3589
|
+
var ALIAS_TO_NAME = /* @__PURE__ */ new Map();
|
|
3590
|
+
for (const framework of FRAMEWORKS) {
|
|
3591
|
+
for (const alias of [framework.name, ...framework.aliases]) {
|
|
3592
|
+
ALIAS_TO_NAME.set(normalize(alias), framework.name);
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
var STRATEGY_BY_NAME = new Map(FRAMEWORKS.map((f) => [f.name, f.strategy]));
|
|
3596
|
+
function canonicalFrameworkName(name) {
|
|
3597
|
+
return ALIAS_TO_NAME.get(normalize(name));
|
|
3598
|
+
}
|
|
3599
|
+
function isSameFramework(a, b) {
|
|
3600
|
+
const x = canonicalFrameworkName(a) ?? normalize(a);
|
|
3601
|
+
const y = canonicalFrameworkName(b) ?? normalize(b);
|
|
2901
3602
|
return x !== "" && x === y;
|
|
2902
|
-
}
|
|
2903
|
-
function
|
|
3603
|
+
}
|
|
3604
|
+
function resolveSearchStrategy(frameworkName, hasJavaScriptInStack) {
|
|
3605
|
+
const canonical = frameworkName ? canonicalFrameworkName(frameworkName) : void 0;
|
|
3606
|
+
const strategy = canonical ? STRATEGY_BY_NAME.get(canonical) : void 0;
|
|
3607
|
+
if (strategy) return strategy;
|
|
3608
|
+
return hasJavaScriptInStack ? "js" : "cdn-template";
|
|
3609
|
+
}
|
|
3610
|
+
function searchDocKey(strategy) {
|
|
3611
|
+
return strategy === "cdn-template" ? "templates" : strategy;
|
|
3612
|
+
}
|
|
3613
|
+
function describeSearchTarget(strategy, frameworkName) {
|
|
3614
|
+
switch (strategy) {
|
|
3615
|
+
case "react":
|
|
3616
|
+
return "React (react-instantsearch)";
|
|
3617
|
+
case "vue":
|
|
3618
|
+
return "Vue (vue-instantsearch)";
|
|
3619
|
+
case "angular":
|
|
3620
|
+
return "Angular (angular-instantsearch)";
|
|
3621
|
+
case "js":
|
|
3622
|
+
return "plain JavaScript (InstantSearch.js)";
|
|
3623
|
+
case "cdn-template":
|
|
3624
|
+
return `${frameworkName ?? "server-rendered"} templates (InstantSearch.js via CDN)`;
|
|
3625
|
+
case "none":
|
|
3626
|
+
return frameworkName ?? "a native mobile app";
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
|
|
3630
|
+
// src/actions/confirmFramework.ts
|
|
3631
|
+
var confirmFrameworkSchema = z20.object({
|
|
3632
|
+
frameworks: detectLanguageSchema.shape.frameworks
|
|
3633
|
+
});
|
|
3634
|
+
var OTHER_OPTION2 = "Other";
|
|
3635
|
+
function confirmed2(name, version) {
|
|
2904
3636
|
const frameworks = [{ name, version: version ?? "unknown" }];
|
|
2905
3637
|
track("AI Wizard Frontend Framework Confirmed", { frameworks });
|
|
2906
3638
|
return { frameworks };
|
|
@@ -2928,7 +3660,7 @@ async function confirmFramework(ctx) {
|
|
|
2928
3660
|
for (const fw of detectedFrameworks) {
|
|
2929
3661
|
if (!options.some((o) => isSameFramework(o, fw.name))) options.push(fw.name);
|
|
2930
3662
|
}
|
|
2931
|
-
options.push(
|
|
3663
|
+
options.push(OTHER_OPTION2);
|
|
2932
3664
|
const detectedFor = (option) => detectedFrameworks.find((fw) => isSameFramework(option, fw.name));
|
|
2933
3665
|
const primary = detectedFrameworks[0];
|
|
2934
3666
|
if (primary) {
|
|
@@ -2938,7 +3670,7 @@ async function confirmFramework(ctx) {
|
|
|
2938
3670
|
options: [`Confirm ${primary.name}`, "Use a different framework"],
|
|
2939
3671
|
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0]
|
|
2940
3672
|
});
|
|
2941
|
-
if (accepted === true) return
|
|
3673
|
+
if (accepted === true) return confirmed2(primary.name, primary.version);
|
|
2942
3674
|
}
|
|
2943
3675
|
const secondary = options.map(
|
|
2944
3676
|
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
@@ -2948,7 +3680,7 @@ async function confirmFramework(ctx) {
|
|
|
2948
3680
|
0
|
|
2949
3681
|
);
|
|
2950
3682
|
const selection = await ctx.requestUserInput({
|
|
2951
|
-
prompt: "select
|
|
3683
|
+
prompt: "select the framework that renders your UI",
|
|
2952
3684
|
promptType: "multipleChoice",
|
|
2953
3685
|
options,
|
|
2954
3686
|
secondary,
|
|
@@ -2957,10 +3689,10 @@ async function confirmFramework(ctx) {
|
|
|
2957
3689
|
if (typeof selection !== "string") {
|
|
2958
3690
|
throw new Error("confirmFramework received an unexpected non-text result");
|
|
2959
3691
|
}
|
|
2960
|
-
if (selection ===
|
|
2961
|
-
return
|
|
3692
|
+
if (selection === OTHER_OPTION2) {
|
|
3693
|
+
return confirmed2(await askOtherFramework(ctx));
|
|
2962
3694
|
}
|
|
2963
|
-
return
|
|
3695
|
+
return confirmed2(selection, detectedFor(selection)?.version);
|
|
2964
3696
|
}
|
|
2965
3697
|
|
|
2966
3698
|
// src/actions/promptUser.ts
|
|
@@ -3053,15 +3785,15 @@ async function confirmEntities(ctx) {
|
|
|
3053
3785
|
onSubmit: () => {
|
|
3054
3786
|
}
|
|
3055
3787
|
});
|
|
3056
|
-
const
|
|
3057
|
-
if (
|
|
3788
|
+
const confirmed3 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
|
|
3789
|
+
if (confirmed3.length === 0) {
|
|
3058
3790
|
throw new Error("User cancelled entity selection \u2014 analysis halted.");
|
|
3059
3791
|
}
|
|
3060
|
-
ctx.setUserInput("confirmedEntities",
|
|
3792
|
+
ctx.setUserInput("confirmedEntities", confirmed3);
|
|
3061
3793
|
track("AI Wizard Entities Confirmed", {
|
|
3062
|
-
entities: toEntitySummary(
|
|
3794
|
+
entities: toEntitySummary(confirmed3)
|
|
3063
3795
|
});
|
|
3064
|
-
return { ingestionAnalysis: entities, confirmedEntities:
|
|
3796
|
+
return { ingestionAnalysis: entities, confirmedEntities: confirmed3 };
|
|
3065
3797
|
}
|
|
3066
3798
|
|
|
3067
3799
|
// src/actions/review.ts
|
|
@@ -3085,7 +3817,7 @@ ${JSON.stringify(s.output, null, 2)}`
|
|
|
3085
3817
|
}
|
|
3086
3818
|
function formatReviewSummary(result) {
|
|
3087
3819
|
const nextStepLines = result.nextSteps.map((step) => {
|
|
3088
|
-
const isIngestCommand = step.includes(".algolia-wizard/
|
|
3820
|
+
const isIngestCommand = step.includes(".algolia-wizard/") || step.includes("AlgoliaWizardIngest");
|
|
3089
3821
|
const isWorktreeCommand = step.includes("/worktrees/");
|
|
3090
3822
|
return {
|
|
3091
3823
|
text: `\u2192 ${step}`,
|
|
@@ -3117,8 +3849,7 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3117
3849
|
],
|
|
3118
3850
|
tools: [],
|
|
3119
3851
|
outputSchema: reviewSchema,
|
|
3120
|
-
modelSize: "small"
|
|
3121
|
-
name: "review"
|
|
3852
|
+
modelSize: "small"
|
|
3122
3853
|
});
|
|
3123
3854
|
ctx.notify({ messages: formatReviewSummary(result) });
|
|
3124
3855
|
return result;
|
|
@@ -3129,12 +3860,13 @@ import z24 from "zod";
|
|
|
3129
3860
|
|
|
3130
3861
|
// src/lib/worktree.ts
|
|
3131
3862
|
import { execFile, spawn as spawn3 } from "node:child_process";
|
|
3132
|
-
import {
|
|
3863
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
3864
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir4, readFile as readFile8, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3133
3865
|
import {
|
|
3134
3866
|
basename as basename2,
|
|
3135
3867
|
dirname as dirname7,
|
|
3136
3868
|
isAbsolute as isAbsolute2,
|
|
3137
|
-
join as
|
|
3869
|
+
join as join12,
|
|
3138
3870
|
relative as relative2,
|
|
3139
3871
|
resolve as resolve3
|
|
3140
3872
|
} from "node:path";
|
|
@@ -3168,8 +3900,8 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
3168
3900
|
return out.trim().length > 0;
|
|
3169
3901
|
}
|
|
3170
3902
|
async function pruneOldWorktrees(repoRoot) {
|
|
3171
|
-
const dir =
|
|
3172
|
-
const stale = (await
|
|
3903
|
+
const dir = join12(stateDir(repoRoot), "worktrees");
|
|
3904
|
+
const stale = (await readdir4(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
3173
3905
|
for (const slug of stale) {
|
|
3174
3906
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
3175
3907
|
try {
|
|
@@ -3179,7 +3911,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3179
3911
|
"worktree",
|
|
3180
3912
|
"remove",
|
|
3181
3913
|
"--force",
|
|
3182
|
-
|
|
3914
|
+
join12(dir, slug)
|
|
3183
3915
|
]);
|
|
3184
3916
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
3185
3917
|
} catch (err) {
|
|
@@ -3193,24 +3925,19 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3193
3925
|
async function createWorktree(repoRoot) {
|
|
3194
3926
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
3195
3927
|
const dirSlug = branch.replace(/\//g, "-");
|
|
3196
|
-
const path =
|
|
3928
|
+
const path = join12(stateDir(repoRoot), "worktrees", dirSlug);
|
|
3197
3929
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
3198
3930
|
await pruneOldWorktrees(repoRoot);
|
|
3199
3931
|
await mkdir6(dirname7(path), { recursive: true });
|
|
3200
3932
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
3201
3933
|
return { path, branch };
|
|
3202
3934
|
}
|
|
3203
|
-
|
|
3204
|
-
try {
|
|
3205
|
-
await readPackageJson(worktreePath);
|
|
3206
|
-
} catch {
|
|
3207
|
-
return { ok: true, output: "no package.json; skipped install" };
|
|
3208
|
-
}
|
|
3209
|
-
const pm = await detectPackageManager(worktreePath);
|
|
3935
|
+
function spawnStep(worktreePath, argv) {
|
|
3210
3936
|
return new Promise((resolve4) => {
|
|
3211
3937
|
let output = "";
|
|
3212
|
-
const child = spawn3(
|
|
3938
|
+
const child = spawn3(argv[0], [...argv.slice(1)], {
|
|
3213
3939
|
cwd: worktreePath,
|
|
3940
|
+
shell: false,
|
|
3214
3941
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3215
3942
|
});
|
|
3216
3943
|
child.stdout?.on("data", (d) => output += d);
|
|
@@ -3219,7 +3946,7 @@ async function installWorktreeDeps(worktreePath) {
|
|
|
3219
3946
|
"error",
|
|
3220
3947
|
(err) => resolve4({
|
|
3221
3948
|
ok: false,
|
|
3222
|
-
output: `Failed to run ${
|
|
3949
|
+
output: `Failed to run ${argv.join(" ")}: ${err.message}`
|
|
3223
3950
|
})
|
|
3224
3951
|
);
|
|
3225
3952
|
child.on(
|
|
@@ -3228,8 +3955,41 @@ async function installWorktreeDeps(worktreePath) {
|
|
|
3228
3955
|
);
|
|
3229
3956
|
});
|
|
3230
3957
|
}
|
|
3231
|
-
|
|
3232
|
-
|
|
3958
|
+
async function installWorktreeDeps(worktreePath, toolchain) {
|
|
3959
|
+
const { profile: profile2, installSteps, packageManager } = toolchain;
|
|
3960
|
+
const declared = packageManager.dependency.mode === "agent-declares" ? packageManager.dependency.file : void 0;
|
|
3961
|
+
const haveSomethingToInstall = await hasProfileManifest(worktreePath, profile2) || declared !== void 0 && existsSync4(join12(worktreePath, declared));
|
|
3962
|
+
if (!haveSomethingToInstall) {
|
|
3963
|
+
return {
|
|
3964
|
+
ok: true,
|
|
3965
|
+
output: `no ${profile2.displayName} manifest; skipped install`
|
|
3966
|
+
};
|
|
3967
|
+
}
|
|
3968
|
+
if (installSteps.length === 0) {
|
|
3969
|
+
return {
|
|
3970
|
+
ok: true,
|
|
3971
|
+
output: `${profile2.displayName} (${toolchain.packageManager.id}) has no wizard-run install step`
|
|
3972
|
+
};
|
|
3973
|
+
}
|
|
3974
|
+
const outputs = [];
|
|
3975
|
+
for (const step of installSteps) {
|
|
3976
|
+
if (step.requiresFile && !existsSync4(join12(worktreePath, step.requiresFile)))
|
|
3977
|
+
continue;
|
|
3978
|
+
const result = await spawnStep(worktreePath, step.argv);
|
|
3979
|
+
if (result.output) outputs.push(result.output);
|
|
3980
|
+
if (result.ok) continue;
|
|
3981
|
+
if (step.optional) {
|
|
3982
|
+
logger.warn(
|
|
3983
|
+
{ step: step.argv.join(" "), output: result.output },
|
|
3984
|
+
"installWorktreeDeps: optional install step failed; continuing"
|
|
3985
|
+
);
|
|
3986
|
+
continue;
|
|
3987
|
+
}
|
|
3988
|
+
return { ok: false, output: outputs.join("\n").trim() };
|
|
3989
|
+
}
|
|
3990
|
+
return { ok: true, output: outputs.join("\n").trim() };
|
|
3991
|
+
}
|
|
3992
|
+
function validateIngestEntrypoint(worktreePath, entrypoint, allowedExtensions) {
|
|
3233
3993
|
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
3234
3994
|
return {
|
|
3235
3995
|
ok: false,
|
|
@@ -3244,18 +4004,29 @@ function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
|
3244
4004
|
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
3245
4005
|
};
|
|
3246
4006
|
}
|
|
4007
|
+
if (allowedExtensions?.length && !allowedExtensions.some((ext) => entrypoint.endsWith(ext))) {
|
|
4008
|
+
return {
|
|
4009
|
+
ok: false,
|
|
4010
|
+
reason: `entrypoint "${entrypoint}" is not one of ${allowedExtensions.join(", ")}`
|
|
4011
|
+
};
|
|
4012
|
+
}
|
|
3247
4013
|
return { ok: true, target };
|
|
3248
4014
|
}
|
|
3249
|
-
async function runIngestScript(worktreePath,
|
|
3250
|
-
|
|
4015
|
+
async function runIngestScript(worktreePath, toolchain, entrypoint, env = {}) {
|
|
4016
|
+
const { ingest, profile: profile2, packageManager } = toolchain;
|
|
4017
|
+
if (ingest.kind !== "auto") {
|
|
3251
4018
|
return {
|
|
3252
4019
|
ran: false,
|
|
3253
4020
|
ok: false,
|
|
3254
4021
|
output: "",
|
|
3255
|
-
reason:
|
|
4022
|
+
reason: `${profile2.displayName} (${packageManager.id}) projects must be run manually: ${ingest.runCommand}`
|
|
3256
4023
|
};
|
|
3257
4024
|
}
|
|
3258
|
-
const validated = validateIngestEntrypoint(
|
|
4025
|
+
const validated = validateIngestEntrypoint(
|
|
4026
|
+
worktreePath,
|
|
4027
|
+
entrypoint,
|
|
4028
|
+
ingest.entrypointExtensions
|
|
4029
|
+
);
|
|
3259
4030
|
if (!validated.ok) {
|
|
3260
4031
|
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
3261
4032
|
}
|
|
@@ -3276,9 +4047,10 @@ async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
|
3276
4047
|
reason: `entrypoint "${entrypoint}" does not exist`
|
|
3277
4048
|
};
|
|
3278
4049
|
}
|
|
4050
|
+
const argv = resolveIngestArgv(ingest, entrypoint);
|
|
3279
4051
|
return new Promise((resolveRun) => {
|
|
3280
4052
|
let output = "";
|
|
3281
|
-
const child = spawn3(
|
|
4053
|
+
const child = spawn3(argv[0], argv.slice(1), {
|
|
3282
4054
|
cwd: worktreePath,
|
|
3283
4055
|
shell: false,
|
|
3284
4056
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -3291,7 +4063,7 @@ async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
|
3291
4063
|
(err) => resolveRun({
|
|
3292
4064
|
ran: true,
|
|
3293
4065
|
ok: false,
|
|
3294
|
-
output: `Failed to run ${
|
|
4066
|
+
output: `Failed to run ${argv.join(" ")}: ${err.message}`
|
|
3295
4067
|
})
|
|
3296
4068
|
);
|
|
3297
4069
|
child.on(
|
|
@@ -3313,8 +4085,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3313
4085
|
} catch {
|
|
3314
4086
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
3315
4087
|
}
|
|
3316
|
-
const relPath =
|
|
3317
|
-
const dest =
|
|
4088
|
+
const relPath = join12(ingestDir, basename2(source));
|
|
4089
|
+
const dest = join12(worktreePath, relPath);
|
|
3318
4090
|
try {
|
|
3319
4091
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
3320
4092
|
await copyFile(source, dest);
|
|
@@ -3330,7 +4102,7 @@ function hasEnvVar(content, name) {
|
|
|
3330
4102
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
3331
4103
|
}
|
|
3332
4104
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
3333
|
-
const target =
|
|
4105
|
+
const target = join12(worktreePath, ".env");
|
|
3334
4106
|
let existing = "";
|
|
3335
4107
|
try {
|
|
3336
4108
|
existing = await readFile8(target, "utf8");
|
|
@@ -3450,69 +4222,33 @@ async function resolveSearchOnlyKey(index) {
|
|
|
3450
4222
|
}
|
|
3451
4223
|
|
|
3452
4224
|
// src/lib/algoliaDocs.ts
|
|
3453
|
-
import { readFileSync,
|
|
3454
|
-
import { dirname as dirname8, join as
|
|
4225
|
+
import { readFileSync, existsSync as existsSync5 } from "node:fs";
|
|
4226
|
+
import { dirname as dirname8, join as join13 } from "node:path";
|
|
3455
4227
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3456
|
-
var DOCS_SUBPATH =
|
|
4228
|
+
var DOCS_SUBPATH = join13("docs", "algolia-sdk");
|
|
3457
4229
|
function findDocsDir() {
|
|
3458
4230
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
3459
4231
|
for (; ; ) {
|
|
3460
|
-
const candidate =
|
|
3461
|
-
if (
|
|
4232
|
+
const candidate = join13(dir, DOCS_SUBPATH);
|
|
4233
|
+
if (existsSync5(candidate)) return candidate;
|
|
3462
4234
|
const parent = dirname8(dir);
|
|
3463
4235
|
if (parent === dir) return void 0;
|
|
3464
4236
|
dir = parent;
|
|
3465
4237
|
}
|
|
3466
4238
|
}
|
|
3467
|
-
function
|
|
3468
|
-
const docsDir = findDocsDir();
|
|
3469
|
-
if (!docsDir) {
|
|
3470
|
-
logger.warn(
|
|
3471
|
-
"algoliaDocs: docs/algolia-sdk not found; skipping SDK reference"
|
|
3472
|
-
);
|
|
3473
|
-
return "";
|
|
3474
|
-
}
|
|
3475
|
-
const files = readdirSync(docsDir).filter((f) => f.includes(language));
|
|
3476
|
-
if (files.length === 0) {
|
|
3477
|
-
logger.warn(
|
|
3478
|
-
{ language },
|
|
3479
|
-
"algoliaDocs: no SDK reference found for language; skipping"
|
|
3480
|
-
);
|
|
3481
|
-
return "";
|
|
3482
|
-
}
|
|
3483
|
-
return readFileSync(join11(docsDir, files[0]), "utf8").trim();
|
|
3484
|
-
}
|
|
3485
|
-
function getNamedDoc(name, language) {
|
|
4239
|
+
function getNamedDoc(name, key) {
|
|
3486
4240
|
const docsDir = findDocsDir();
|
|
3487
4241
|
if (!docsDir) {
|
|
3488
4242
|
logger.warn("docs/algolia-sdk not found");
|
|
3489
4243
|
return "";
|
|
3490
4244
|
}
|
|
3491
|
-
const file =
|
|
3492
|
-
if (!
|
|
3493
|
-
logger.warn({ name,
|
|
4245
|
+
const file = join13(docsDir, `${name}-${key}.md`);
|
|
4246
|
+
if (!existsSync5(file)) {
|
|
4247
|
+
logger.warn({ name, key }, "named SDK reference not found");
|
|
3494
4248
|
return "";
|
|
3495
4249
|
}
|
|
3496
4250
|
return readFileSync(file, "utf8").trim();
|
|
3497
4251
|
}
|
|
3498
|
-
function getFrameworkSpecificDoc(frameworks) {
|
|
3499
|
-
const fw = frameworks.map((f) => f.toLowerCase());
|
|
3500
|
-
if (fw.includes("vue") || fw.includes("nuxt")) {
|
|
3501
|
-
return loadAlgoliaDoc("vue");
|
|
3502
|
-
}
|
|
3503
|
-
if (fw.includes("react") || fw.includes("next.js")) {
|
|
3504
|
-
return loadAlgoliaDoc("react");
|
|
3505
|
-
}
|
|
3506
|
-
if (fw.includes("angular")) {
|
|
3507
|
-
return loadAlgoliaDoc("angular");
|
|
3508
|
-
}
|
|
3509
|
-
return loadAlgoliaDoc("js");
|
|
3510
|
-
}
|
|
3511
|
-
|
|
3512
|
-
// src/lib/shell.ts
|
|
3513
|
-
function shellQuote(value) {
|
|
3514
|
-
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
3515
|
-
}
|
|
3516
4252
|
|
|
3517
4253
|
// src/actions/implement.ts
|
|
3518
4254
|
var implementSchema = z24.object({
|
|
@@ -3547,12 +4283,11 @@ var implementSchema = z24.object({
|
|
|
3547
4283
|
});
|
|
3548
4284
|
var implementationOutputSchema = z24.object({
|
|
3549
4285
|
summary: z24.string(),
|
|
3550
|
-
// Ingestion only:
|
|
3551
|
-
//
|
|
3552
|
-
//
|
|
3553
|
-
//
|
|
3554
|
-
// the agent
|
|
3555
|
-
runtime: z24.enum(INGEST_RUNTIMES).optional(),
|
|
4286
|
+
// Ingestion only: the script the wizard should run, as a bare path — never a
|
|
4287
|
+
// command string, and never the interpreter. The command comes from the
|
|
4288
|
+
// resolved language toolchain (a registry constant); this path is validated to
|
|
4289
|
+
// a worktree-relative file with a runnable extension and substituted into it.
|
|
4290
|
+
// So the agent contributes no part of the command that gets executed.
|
|
3556
4291
|
entrypoint: z24.string().optional()
|
|
3557
4292
|
});
|
|
3558
4293
|
var verificationOutputSchema = z24.object({
|
|
@@ -3562,28 +4297,9 @@ var verificationOutputSchema = z24.object({
|
|
|
3562
4297
|
});
|
|
3563
4298
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
3564
4299
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
3565
|
-
var
|
|
3566
|
-
function
|
|
3567
|
-
|
|
3568
|
-
if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
|
|
3569
|
-
if (names.some((n) => n.includes("react") || n.includes("next")))
|
|
3570
|
-
return "React";
|
|
3571
|
-
if (names.some((n) => n.includes("angular"))) return "Angular";
|
|
3572
|
-
return "JavaScript";
|
|
3573
|
-
}
|
|
3574
|
-
function frameworksForDoc(framework) {
|
|
3575
|
-
switch (framework) {
|
|
3576
|
-
case "React":
|
|
3577
|
-
return ["react"];
|
|
3578
|
-
case "Vue":
|
|
3579
|
-
return ["vue"];
|
|
3580
|
-
case "Angular":
|
|
3581
|
-
return ["angular"];
|
|
3582
|
-
case "JavaScript":
|
|
3583
|
-
return [];
|
|
3584
|
-
}
|
|
3585
|
-
}
|
|
3586
|
-
function publicEnvPrefix(language) {
|
|
4300
|
+
var INGEST_DIR2 = ".algolia-wizard";
|
|
4301
|
+
function publicEnvPrefix(language, strategy) {
|
|
4302
|
+
if (strategy === "cdn-template" || strategy === "none") return "";
|
|
3587
4303
|
const frameworkNames = language.frameworks.map(
|
|
3588
4304
|
(framework) => framework.name.toLowerCase()
|
|
3589
4305
|
);
|
|
@@ -3601,8 +4317,8 @@ function publicEnvPrefix(language) {
|
|
|
3601
4317
|
}
|
|
3602
4318
|
return "PUBLIC_";
|
|
3603
4319
|
}
|
|
3604
|
-
function searchEnvVars(language, appId, searchKey) {
|
|
3605
|
-
const prefix = publicEnvPrefix(language);
|
|
4320
|
+
function searchEnvVars(language, strategy, appId, searchKey) {
|
|
4321
|
+
const prefix = publicEnvPrefix(language, strategy);
|
|
3606
4322
|
return [
|
|
3607
4323
|
{
|
|
3608
4324
|
name: `${prefix}ALGOLIA_APP_ID`,
|
|
@@ -3614,6 +4330,42 @@ function searchEnvVars(language, appId, searchKey) {
|
|
|
3614
4330
|
}
|
|
3615
4331
|
];
|
|
3616
4332
|
}
|
|
4333
|
+
async function resolveIngestionProfile(ctx, language, repoRoot) {
|
|
4334
|
+
const fallback = LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID];
|
|
4335
|
+
const confirmed3 = language.languages.map((l) => resolveLanguageProfile(l.name)).filter((p) => p !== void 0);
|
|
4336
|
+
const onDisk = await detectProfilesFromManifests(repoRoot);
|
|
4337
|
+
const onDiskIds = new Set(onDisk.map((p) => p.id));
|
|
4338
|
+
const candidates = [
|
|
4339
|
+
...new Map(
|
|
4340
|
+
confirmed3.filter((p) => onDiskIds.has(p.id)).map((p) => [p.id, p])
|
|
4341
|
+
).values()
|
|
4342
|
+
];
|
|
4343
|
+
if (candidates.length === 0) {
|
|
4344
|
+
const chosen = confirmed3[0] ?? onDisk[0] ?? fallback;
|
|
4345
|
+
logger.warn(
|
|
4346
|
+
{
|
|
4347
|
+
confirmed: language.languages.map((l) => l.name),
|
|
4348
|
+
onDisk: onDisk.map((p) => p.id),
|
|
4349
|
+
chosen: chosen.id
|
|
4350
|
+
},
|
|
4351
|
+
"implement: no confirmed language matched a manifest on disk; falling back"
|
|
4352
|
+
);
|
|
4353
|
+
return chosen;
|
|
4354
|
+
}
|
|
4355
|
+
if (candidates.length === 1) return candidates[0];
|
|
4356
|
+
const backends = candidates.filter((p) => p.id !== DEFAULT_LANGUAGE_ID);
|
|
4357
|
+
if (backends.length === 1) return backends[0];
|
|
4358
|
+
if (backends.length === 0) return candidates[0];
|
|
4359
|
+
const options = backends.map((p) => p.displayName);
|
|
4360
|
+
const selection = await ctx.requestUserInput({
|
|
4361
|
+
prompt: "Which language should the ingestion script use?",
|
|
4362
|
+
promptType: "multipleChoice",
|
|
4363
|
+
options,
|
|
4364
|
+
defaultSelectedIndex: 0
|
|
4365
|
+
});
|
|
4366
|
+
const picked = typeof selection === "string" ? backends.find((p) => p.displayName === selection) : void 0;
|
|
4367
|
+
return picked ?? backends[0];
|
|
4368
|
+
}
|
|
3617
4369
|
function baseInstructions(input) {
|
|
3618
4370
|
return [
|
|
3619
4371
|
`Target Algolia index: ${input.targetIndex}`,
|
|
@@ -3641,37 +4393,48 @@ function sourceSpecificInstructions(input) {
|
|
|
3641
4393
|
generated: [
|
|
3642
4394
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
3643
4395
|
"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.",
|
|
3644
|
-
"In the script, read and parse each returned file path at runtime
|
|
4396
|
+
"In the script, read and parse each returned JSON file path at runtime using the idiomatic file read for the language you are writing in (the SDK reference above shows one) instead of inlining the records as literals.",
|
|
3645
4397
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
3646
4398
|
]
|
|
3647
4399
|
};
|
|
3648
4400
|
return byLine[input.ingestionSource];
|
|
3649
4401
|
}
|
|
3650
4402
|
function ingestionInstructions(input) {
|
|
4403
|
+
const { ingestionProfile: profile2, toolchain } = input;
|
|
4404
|
+
const { ingest } = toolchain;
|
|
4405
|
+
const extensions = ingest.entrypointExtensions.join(", ");
|
|
4406
|
+
const runInstruction = ingest.kind === "auto" ? `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile2.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard runs it with \`${describeIngestCommand(ingest, profile2.ingestEntrypointExample)}\`, so it must be a plain path with no flags or arguments and must run as-is under that command.` : `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile2.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard does NOT run ${profile2.displayName} ${toolchain.packageManager.id} projects itself \u2014 it tells the developer to run \`${describeIngestCommand(ingest, profile2.ingestEntrypointExample)}\`, so also add whatever build configuration that command needs.`;
|
|
3651
4407
|
return [
|
|
3652
4408
|
...input.confirmed && input.confirmed.length ? [
|
|
3653
|
-
`
|
|
4409
|
+
`Write the ingestion script in ${profile2.displayName}, at "${ingestScriptDir(profile2)}/" in the repo.`,
|
|
3654
4410
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
3655
|
-
`Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them. The wizard sets these when it runs the script.`,
|
|
3656
|
-
|
|
4411
|
+
`Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them. ${profile2.envReadInstruction} The wizard sets these when it runs the script.`,
|
|
4412
|
+
`Use the official Algolia ${profile2.displayName} client (${profile2.sdk.packageName}). Do not use the raw HTTP API, and do not use a client for another language.`,
|
|
3657
4413
|
"After a successful ingest, the script must print exactly one line to stdout in the form `ALGOLIA_WIZARD_RECORD_COUNT=<n>`, where <n> is the total number of records pushed to Algolia. Print it last, on its own line, with no surrounding text.",
|
|
3658
|
-
getNamedDoc("save-records",
|
|
3659
|
-
|
|
4414
|
+
getNamedDoc("save-records", profile2.sdk.docKey),
|
|
4415
|
+
dependencyInstruction(toolchain),
|
|
3660
4416
|
"The summary should be extremely concise.",
|
|
3661
|
-
|
|
4417
|
+
runInstruction,
|
|
3662
4418
|
...sourceSpecificInstructions(input)
|
|
3663
4419
|
] : []
|
|
3664
4420
|
];
|
|
3665
4421
|
}
|
|
3666
4422
|
function searchInstructions(input) {
|
|
3667
|
-
const doc =
|
|
4423
|
+
const doc = getNamedDoc(
|
|
4424
|
+
"instantsearch-setup",
|
|
4425
|
+
searchDocKey(input.searchStrategy)
|
|
4426
|
+
);
|
|
4427
|
+
const isTemplate = input.searchStrategy === "cdn-template";
|
|
4428
|
+
const placement = isTemplate ? input.searchLocation ? `Add the search UI to the server-rendered template at "${input.searchLocation}" \u2014 ideally a shared layout, so it is reachable across the app.` : `This project has no shared template to host the UI, so create a standalone page at "${input.ingestDir}/search-demo.html" the developer can open directly, and add a TODO explaining how to move the snippet into their own layout.` : `Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app.`;
|
|
3668
4429
|
return [
|
|
3669
4430
|
"Implement an in-app Algolia search experience.",
|
|
3670
|
-
`Build the search UI for ${input.
|
|
3671
|
-
"Follow the Algolia
|
|
4431
|
+
`Build the search UI for ${describeSearchTarget(input.searchStrategy, input.frameworkName)}.`,
|
|
4432
|
+
"Follow the Algolia reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3672
4433
|
doc,
|
|
3673
|
-
|
|
3674
|
-
|
|
4434
|
+
placement,
|
|
4435
|
+
`It needs at least a working SearchBox and Hits against the "${input.targetIndex}" index.`,
|
|
4436
|
+
isTemplate ? "Load InstantSearch from a CDN with script tags as shown in the reference. Do not add JavaScript package dependencies, a bundler, or a build step." : 'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.',
|
|
4437
|
+
isTemplate ? "Read the App ID and search-only API key from server-side configuration/environment and render them into the page (e.g. as data- attributes the script reads); never hardcode them, and never put a write/admin key in HTML." : "Read the App ID and a search-only API key from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
3675
4438
|
// appId always resolves (loadActiveProfile throws otherwise); only the
|
|
3676
4439
|
// search-only key is best-effort and can fall back to a placeholder.
|
|
3677
4440
|
`Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
@@ -3679,8 +4442,7 @@ function searchInstructions(input) {
|
|
|
3679
4442
|
// resolved app id / search-only key into ".env" under these exact names
|
|
3680
4443
|
// right after this step, so a renamed prefix here would leave the code
|
|
3681
4444
|
// reading a var the wizard never wrote.
|
|
3682
|
-
`Use exactly these
|
|
3683
|
-
'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.',
|
|
4445
|
+
`Use exactly these env var names: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3684
4446
|
"The summary should be extremely concise; do not mention env var setup or manual testing steps \u2014 the wizard writes the resolved credentials to .env and reports that separately."
|
|
3685
4447
|
];
|
|
3686
4448
|
}
|
|
@@ -3688,7 +4450,7 @@ function verificationInstructions(input) {
|
|
|
3688
4450
|
return [
|
|
3689
4451
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
3690
4452
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
3691
|
-
|
|
4453
|
+
`Call verifyImplementation at least once; it runs the mechanical checks available for this repo's languages (${input.verificationLanguages.join(", ")}) and returns per-check results plus an aggregate ok.`,
|
|
3692
4454
|
"For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
|
|
3693
4455
|
"Do not make speculative fixes when verifyImplementation cannot run, no checks exist, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
|
|
3694
4456
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
@@ -3743,8 +4505,8 @@ function formatSummary(useCase, summary) {
|
|
|
3743
4505
|
const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
|
|
3744
4506
|
return `${label}: ${summary}`;
|
|
3745
4507
|
}
|
|
3746
|
-
function buildIngestCommand(worktree,
|
|
3747
|
-
return `cd ${shellQuote(worktree)} && ${
|
|
4508
|
+
function buildIngestCommand(worktree, toolchain, entrypoint) {
|
|
4509
|
+
return `cd ${shellQuote(worktree)} && ${describeIngestCommand(toolchain.ingest, entrypoint)}`;
|
|
3748
4510
|
}
|
|
3749
4511
|
function parseIngestRecordCount(output) {
|
|
3750
4512
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
@@ -3826,7 +4588,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3826
4588
|
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
3827
4589
|
}
|
|
3828
4590
|
const normalized = normalizeFindingPaths(findings);
|
|
3829
|
-
const
|
|
4591
|
+
const confirmed3 = normalized.confirmedEntities;
|
|
3830
4592
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
3831
4593
|
let appId;
|
|
3832
4594
|
let searchKey;
|
|
@@ -3850,7 +4612,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3850
4612
|
const copied = await copyUploadIntoWorktree(
|
|
3851
4613
|
repoRoot,
|
|
3852
4614
|
worktree,
|
|
3853
|
-
|
|
4615
|
+
INGEST_DIR2,
|
|
3854
4616
|
uploadSourcePath ?? ""
|
|
3855
4617
|
);
|
|
3856
4618
|
if (copied.ok) {
|
|
@@ -3864,26 +4626,59 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3864
4626
|
);
|
|
3865
4627
|
}
|
|
3866
4628
|
}
|
|
4629
|
+
const ingestionProfile = await resolveIngestionProfile(
|
|
4630
|
+
ctx,
|
|
4631
|
+
language,
|
|
4632
|
+
worktree
|
|
4633
|
+
);
|
|
4634
|
+
const toolchain = await resolveToolchain(worktree, ingestionProfile);
|
|
4635
|
+
const verificationLanguages = [
|
|
4636
|
+
.../* @__PURE__ */ new Set([
|
|
4637
|
+
ingestionProfile.id,
|
|
4638
|
+
...(await detectProfilesFromManifests(worktree)).map((p) => p.id)
|
|
4639
|
+
])
|
|
4640
|
+
];
|
|
4641
|
+
const frameworkName = language.frameworks[0]?.name;
|
|
4642
|
+
const searchStrategy = resolveSearchStrategy(
|
|
4643
|
+
frameworkName,
|
|
4644
|
+
verificationLanguages.includes(DEFAULT_LANGUAGE_ID)
|
|
4645
|
+
);
|
|
4646
|
+
logger.info(
|
|
4647
|
+
{
|
|
4648
|
+
language: ingestionProfile.id,
|
|
4649
|
+
packageManager: toolchain.packageManager.id,
|
|
4650
|
+
ingest: toolchain.ingest.kind,
|
|
4651
|
+
framework: frameworkName,
|
|
4652
|
+
searchStrategy
|
|
4653
|
+
},
|
|
4654
|
+
"implement: resolved ingestion toolchain and search strategy"
|
|
4655
|
+
);
|
|
3867
4656
|
const input = {
|
|
3868
4657
|
findings: normalized,
|
|
3869
|
-
confirmed:
|
|
4658
|
+
confirmed: confirmed3,
|
|
3870
4659
|
searchLocation,
|
|
3871
4660
|
targetIndex,
|
|
3872
4661
|
language,
|
|
3873
4662
|
appId,
|
|
3874
4663
|
searchKey,
|
|
3875
|
-
searchEnvVars: searchEnvVars(language, appId, searchKey),
|
|
3876
|
-
ingestDir:
|
|
4664
|
+
searchEnvVars: searchEnvVars(language, searchStrategy, appId, searchKey),
|
|
4665
|
+
ingestDir: INGEST_DIR2,
|
|
3877
4666
|
ingestionSource,
|
|
3878
4667
|
uploadFilePath,
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
4668
|
+
searchStrategy,
|
|
4669
|
+
frameworkName,
|
|
4670
|
+
ingestionProfile,
|
|
4671
|
+
toolchain,
|
|
4672
|
+
verificationLanguages
|
|
3882
4673
|
};
|
|
4674
|
+
const searchToolchain = searchStrategy === "cdn-template" || searchStrategy === "none" ? void 0 : ingestionProfile.id === DEFAULT_LANGUAGE_ID ? toolchain : await resolveToolchain(
|
|
4675
|
+
worktree,
|
|
4676
|
+
LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID]
|
|
4677
|
+
);
|
|
4678
|
+
const toolchainForUseCase = (useCase) => useCase === "search" ? searchToolchain : toolchain;
|
|
3883
4679
|
const summaries = [];
|
|
3884
4680
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
3885
4681
|
let agentRuns = 0;
|
|
3886
|
-
let ingestRuntime;
|
|
3887
4682
|
let ingestEntrypoint;
|
|
3888
4683
|
let ingestScriptRan = false;
|
|
3889
4684
|
let ingestRecordCount;
|
|
@@ -3900,16 +4695,18 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3900
4695
|
extraInstructions
|
|
3901
4696
|
),
|
|
3902
4697
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
3903
|
-
outputSchema: implementationOutputSchema
|
|
3904
|
-
name: `implement:${currentUseCase}`
|
|
4698
|
+
outputSchema: implementationOutputSchema
|
|
3905
4699
|
});
|
|
4700
|
+
const useCaseToolchain = toolchainForUseCase(currentUseCase);
|
|
4701
|
+
if (!useCaseToolchain) return result;
|
|
3906
4702
|
ctx.notify({
|
|
3907
4703
|
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
3908
4704
|
});
|
|
3909
4705
|
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
3910
|
-
useCase: currentUseCase
|
|
4706
|
+
useCase: currentUseCase,
|
|
4707
|
+
language: useCaseToolchain.profile.id
|
|
3911
4708
|
});
|
|
3912
|
-
const install = await installWorktreeDeps(worktree);
|
|
4709
|
+
const install = await installWorktreeDeps(worktree, useCaseToolchain);
|
|
3913
4710
|
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
3914
4711
|
if (!install.ok) {
|
|
3915
4712
|
installFailed = true;
|
|
@@ -3927,15 +4724,15 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3927
4724
|
instructions: buildAgentInstructions("verification", input),
|
|
3928
4725
|
tools: toolsForUseCase("verification"),
|
|
3929
4726
|
outputSchema: verificationOutputSchema,
|
|
3930
|
-
|
|
4727
|
+
// So verifyImplementation runs this repo's checks, not just npm scripts.
|
|
4728
|
+
languages: input.verificationLanguages
|
|
3931
4729
|
});
|
|
3932
4730
|
}
|
|
3933
4731
|
if (useCases.includes("ingestion")) {
|
|
3934
|
-
const { summary,
|
|
4732
|
+
const { summary, entrypoint } = await runImplementationUseCase("ingestion");
|
|
3935
4733
|
summaries.push(formatSummary("ingestion", summary));
|
|
3936
|
-
ingestRuntime = runtime;
|
|
3937
4734
|
ingestEntrypoint = entrypoint;
|
|
3938
|
-
if (
|
|
4735
|
+
if (ingestEntrypoint && toolchain.ingest.kind === "auto" && !installFailed) {
|
|
3939
4736
|
ctx.clearNotices();
|
|
3940
4737
|
const runNow = await ctx.requestUserInput({
|
|
3941
4738
|
prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
|
|
@@ -3947,13 +4744,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3947
4744
|
const profile2 = await loadActiveProfile();
|
|
3948
4745
|
ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
|
|
3949
4746
|
const scriptLogId = ctx.logStart("runIngestScript", {
|
|
3950
|
-
|
|
4747
|
+
language: ingestionProfile.id,
|
|
3951
4748
|
entrypoint: ingestEntrypoint
|
|
3952
4749
|
});
|
|
3953
4750
|
const startedAt = Date.now();
|
|
3954
4751
|
const run = await runIngestScript(
|
|
3955
4752
|
worktree,
|
|
3956
|
-
|
|
4753
|
+
toolchain,
|
|
3957
4754
|
ingestEntrypoint,
|
|
3958
4755
|
{
|
|
3959
4756
|
[APP_ID_VAR]: profile2.appId,
|
|
@@ -3967,7 +4764,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3967
4764
|
ingestRecordCount = parseIngestRecordCount(run.output);
|
|
3968
4765
|
if (ingestRecordCount != null) {
|
|
3969
4766
|
track("AI Wizard Ingest Successful", {
|
|
3970
|
-
entity_name:
|
|
4767
|
+
entity_name: confirmed3?.map((e) => e.name).join(", ") || "unknown",
|
|
3971
4768
|
record_count: ingestRecordCount,
|
|
3972
4769
|
duration_ms: ingestDurationMs
|
|
3973
4770
|
});
|
|
@@ -3980,7 +4777,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3980
4777
|
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run.reason}`;
|
|
3981
4778
|
logger.warn(
|
|
3982
4779
|
{
|
|
3983
|
-
|
|
4780
|
+
language: ingestionProfile.id,
|
|
3984
4781
|
entrypoint: ingestEntrypoint,
|
|
3985
4782
|
reason: run.reason
|
|
3986
4783
|
},
|
|
@@ -4003,7 +4800,7 @@ ${run.output}` : status;
|
|
|
4003
4800
|
outcomeMessage = `\u274C Ingestion failed.${run.output ? ` ${run.output}` : ""}`;
|
|
4004
4801
|
logger.warn(
|
|
4005
4802
|
{
|
|
4006
|
-
|
|
4803
|
+
language: ingestionProfile.id,
|
|
4007
4804
|
entrypoint: ingestEntrypoint,
|
|
4008
4805
|
output: run.output
|
|
4009
4806
|
},
|
|
@@ -4020,10 +4817,15 @@ ${run.output}` : status;
|
|
|
4020
4817
|
}
|
|
4021
4818
|
}
|
|
4022
4819
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
4023
|
-
if (
|
|
4820
|
+
if (ingestEntrypoint) {
|
|
4024
4821
|
commandMessages.push(
|
|
4025
|
-
`Ingestion command: ${buildIngestCommand(worktree,
|
|
4822
|
+
`Ingestion command: ${buildIngestCommand(worktree, toolchain, ingestEntrypoint)}`
|
|
4026
4823
|
);
|
|
4824
|
+
if (toolchain.ingest.kind === "manual") {
|
|
4825
|
+
commandMessages.push(
|
|
4826
|
+
`The wizard does not run ${ingestionProfile.displayName} ${toolchain.packageManager.id} projects \u2014 run the command above yourself to ingest.`
|
|
4827
|
+
);
|
|
4828
|
+
}
|
|
4027
4829
|
}
|
|
4028
4830
|
await ctx.requestUserInput({
|
|
4029
4831
|
// No question being asked here, just an acknowledgement — the
|
|
@@ -4034,7 +4836,21 @@ ${run.output}` : status;
|
|
|
4034
4836
|
messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
|
|
4035
4837
|
});
|
|
4036
4838
|
}
|
|
4037
|
-
|
|
4839
|
+
const skipSearch = useCases.includes("search") && input.searchStrategy === "none";
|
|
4840
|
+
if (skipSearch) {
|
|
4841
|
+
const target = describeSearchTarget(
|
|
4842
|
+
input.searchStrategy,
|
|
4843
|
+
input.frameworkName
|
|
4844
|
+
);
|
|
4845
|
+
summaries.push(
|
|
4846
|
+
`Search UI skipped: the wizard can't scaffold a native search UI for ${target}. Your records are in the "${targetIndex}" index \u2014 build the UI with Algolia's mobile InstantSearch libraries (https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/ios/ for iOS, .../android for Android).`
|
|
4847
|
+
);
|
|
4848
|
+
ctx.setUserInput("implementation", "success");
|
|
4849
|
+
track("AI Wizard Search UI Skipped", {
|
|
4850
|
+
framework: input.frameworkName ?? "unknown"
|
|
4851
|
+
});
|
|
4852
|
+
}
|
|
4853
|
+
if (useCases.includes("search") && !skipSearch) {
|
|
4038
4854
|
let extraInstructions = [];
|
|
4039
4855
|
const preSearchFiles = new Set(await listChangedFiles(worktree));
|
|
4040
4856
|
for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
|
|
@@ -4107,7 +4923,7 @@ ${run.output}` : status;
|
|
|
4107
4923
|
}
|
|
4108
4924
|
if (installFailed) {
|
|
4109
4925
|
summaries.push(
|
|
4110
|
-
|
|
4926
|
+
`\u26A0\uFE0F Dependency install in the worktree failed. Install the ${ingestionProfile.displayName} dependencies in the worktree before the command below, or it will fail on a missing package.`
|
|
4111
4927
|
);
|
|
4112
4928
|
}
|
|
4113
4929
|
return {
|
|
@@ -4115,10 +4931,10 @@ ${run.output}` : status;
|
|
|
4115
4931
|
filesChanged,
|
|
4116
4932
|
summary: summaries.join("\n\n"),
|
|
4117
4933
|
worktreePath: worktree,
|
|
4118
|
-
...useCases.includes("ingestion") &&
|
|
4934
|
+
...useCases.includes("ingestion") && ingestEntrypoint ? {
|
|
4119
4935
|
ingestCommand: buildIngestCommand(
|
|
4120
4936
|
worktree,
|
|
4121
|
-
|
|
4937
|
+
toolchain,
|
|
4122
4938
|
ingestEntrypoint
|
|
4123
4939
|
),
|
|
4124
4940
|
ingestScriptRan,
|