@algolia/wizard 0.8.0-rc.58.44 → 0.8.0-rc.59.47
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
CHANGED
|
@@ -532,8 +532,6 @@ var CANCEL = "cancel";
|
|
|
532
532
|
var ARROW_WIDTH = 4;
|
|
533
533
|
var COLUMN_GAP = 2;
|
|
534
534
|
var BAR_PADDING = 2;
|
|
535
|
-
var ROW_HEIGHT = 3;
|
|
536
|
-
var INDICATOR_ROWS = 2;
|
|
537
535
|
function fittedWidth(node, columns) {
|
|
538
536
|
let left = 0;
|
|
539
537
|
for (let n = node; n; n = n.parentNode) {
|
|
@@ -566,18 +564,13 @@ function SelectPrompt({
|
|
|
566
564
|
if (multi) hints.push({ key: "[space]", label: "select" });
|
|
567
565
|
hints.push({ key: "[enter]", label: "confirm" });
|
|
568
566
|
const containerRef = useRef2(null);
|
|
569
|
-
const
|
|
570
|
-
const { columns, rows: windowRows } = useWindowSize3();
|
|
567
|
+
const { columns } = useWindowSize3();
|
|
571
568
|
const [width, setWidth] = useState3(columns);
|
|
572
|
-
const [viewportHeight, setViewportHeight] = useState3(null);
|
|
573
569
|
useLayoutEffect(() => {
|
|
574
570
|
if (containerRef.current) {
|
|
575
571
|
setWidth(fittedWidth(containerRef.current, columns));
|
|
576
572
|
}
|
|
577
|
-
|
|
578
|
-
setViewportHeight(measureElement2(viewportRef.current).height);
|
|
579
|
-
}
|
|
580
|
-
}, [columns, windowRows, error, question, helpText, messages, table]);
|
|
573
|
+
}, [columns]);
|
|
581
574
|
const inner = Math.max(width - BAR_PADDING, 0);
|
|
582
575
|
const labelWidth = Math.min(
|
|
583
576
|
ARROW_WIDTH + (multi ? 2 : 0) + Math.max(0, ...rows.map((opt) => opt.length)) + COLUMN_GAP,
|
|
@@ -593,22 +586,6 @@ function SelectPrompt({
|
|
|
593
586
|
const barWidth = Math.min(labelWidth + badgeWidth + BAR_PADDING, width);
|
|
594
587
|
const barLabelWidth = Math.max(barWidth - BAR_PADDING - badgeWidth, 0);
|
|
595
588
|
const textWidth = inner - labelWidth;
|
|
596
|
-
const capacity = viewportHeight === null || rows.length * ROW_HEIGHT <= viewportHeight ? rows.length : Math.max(Math.floor((viewportHeight - INDICATOR_ROWS) / ROW_HEIGHT), 1);
|
|
597
|
-
const maxOffset = Math.max(rows.length - capacity, 0);
|
|
598
|
-
const [offset, setOffset] = useState3(0);
|
|
599
|
-
useLayoutEffect(() => {
|
|
600
|
-
setOffset((o) => {
|
|
601
|
-
const clamped = Math.min(o, maxOffset);
|
|
602
|
-
if (index < clamped) return index;
|
|
603
|
-
if (index >= clamped + capacity) {
|
|
604
|
-
return Math.min(index - capacity + 1, maxOffset);
|
|
605
|
-
}
|
|
606
|
-
return clamped;
|
|
607
|
-
});
|
|
608
|
-
}, [index, capacity, maxOffset]);
|
|
609
|
-
const visible = rows.slice(offset, offset + capacity);
|
|
610
|
-
const hiddenAbove = offset;
|
|
611
|
-
const hiddenBelow = rows.length - offset - visible.length;
|
|
612
589
|
useInput((input, key) => {
|
|
613
590
|
if (rows.length === 0) return;
|
|
614
591
|
if (key.upArrow || input === "k") {
|
|
@@ -633,71 +610,56 @@ function SelectPrompt({
|
|
|
633
610
|
}
|
|
634
611
|
});
|
|
635
612
|
return /* @__PURE__ */ jsx4(Box4, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 1, width, children: [
|
|
636
|
-
/* @__PURE__ */
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
/* @__PURE__ */
|
|
641
|
-
|
|
642
|
-
helpText && /* @__PURE__ */ jsx4(Text4, { color: COLORS.dim, children: helpText })
|
|
643
|
-
] })
|
|
644
|
-
] }),
|
|
645
|
-
/* @__PURE__ */ jsxs3(Box4, { ref: viewportRef, flexDirection: "column", flexGrow: 1, children: [
|
|
646
|
-
hiddenAbove > 0 && /* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
|
|
647
|
-
"\u2191 ",
|
|
648
|
-
hiddenAbove,
|
|
649
|
-
" more"
|
|
650
|
-
] }),
|
|
651
|
-
visible.map((option, visibleIndex) => {
|
|
652
|
-
const i = offset + visibleIndex;
|
|
653
|
-
const highlighted = i === index;
|
|
654
|
-
const isCancel = i === cancelIndex;
|
|
655
|
-
const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
|
|
656
|
-
const sec = isCancel ? void 0 : secondary?.[i];
|
|
657
|
-
const labelColor = highlighted ? COLORS.highlight.fg : void 0;
|
|
658
|
-
const label = /* @__PURE__ */ jsxs3(Text4, { color: labelColor, wrap: "truncate", children: [
|
|
659
|
-
highlighted ? "\u276F " : " ",
|
|
660
|
-
bullet,
|
|
661
|
-
option
|
|
662
|
-
] });
|
|
663
|
-
const isText = sec?.kind === "text";
|
|
664
|
-
return /* @__PURE__ */ jsxs3(
|
|
665
|
-
Box4,
|
|
666
|
-
{
|
|
667
|
-
width: isText ? "100%" : barWidth,
|
|
668
|
-
paddingX: 1,
|
|
669
|
-
paddingY: 1,
|
|
670
|
-
backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
|
|
671
|
-
children: [
|
|
672
|
-
/* @__PURE__ */ jsx4(Box4, { width: isText ? labelWidth : barLabelWidth, children: label }),
|
|
673
|
-
isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box4, { width: textWidth, children: /* @__PURE__ */ jsx4(
|
|
674
|
-
Text4,
|
|
675
|
-
{
|
|
676
|
-
wrap: "truncate",
|
|
677
|
-
color: highlighted ? COLORS.primary : COLORS.muted,
|
|
678
|
-
children: sec.value
|
|
679
|
-
}
|
|
680
|
-
) }),
|
|
681
|
-
sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box4, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text4, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
|
|
682
|
-
]
|
|
683
|
-
},
|
|
684
|
-
`row-${i}`
|
|
685
|
-
);
|
|
686
|
-
}),
|
|
687
|
-
hiddenBelow > 0 && /* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
|
|
688
|
-
"\u2193 ",
|
|
689
|
-
hiddenBelow,
|
|
690
|
-
" more"
|
|
691
|
-
] })
|
|
613
|
+
error && /* @__PURE__ */ jsx4(Text4, { color: COLORS.danger, children: error }),
|
|
614
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
615
|
+
table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
|
|
616
|
+
/* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
|
|
617
|
+
question && /* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: question }),
|
|
618
|
+
helpText && /* @__PURE__ */ jsx4(Text4, { color: COLORS.dim, children: helpText })
|
|
692
619
|
] }),
|
|
693
|
-
/* @__PURE__ */ jsx4(Box4, {
|
|
620
|
+
/* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: rows.map((option, i) => {
|
|
621
|
+
const highlighted = i === index;
|
|
622
|
+
const isCancel = i === cancelIndex;
|
|
623
|
+
const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
|
|
624
|
+
const sec = isCancel ? void 0 : secondary?.[i];
|
|
625
|
+
const labelColor = highlighted ? COLORS.highlight.fg : void 0;
|
|
626
|
+
const label = /* @__PURE__ */ jsxs3(Text4, { color: labelColor, wrap: "truncate", children: [
|
|
627
|
+
highlighted ? "\u276F " : " ",
|
|
628
|
+
bullet,
|
|
629
|
+
option
|
|
630
|
+
] });
|
|
631
|
+
const isText = sec?.kind === "text";
|
|
632
|
+
return /* @__PURE__ */ jsxs3(
|
|
633
|
+
Box4,
|
|
634
|
+
{
|
|
635
|
+
width: isText ? "100%" : barWidth,
|
|
636
|
+
paddingX: 1,
|
|
637
|
+
paddingY: 1,
|
|
638
|
+
backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
|
|
639
|
+
children: [
|
|
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 }) })
|
|
650
|
+
]
|
|
651
|
+
},
|
|
652
|
+
`row-${i}`
|
|
653
|
+
);
|
|
654
|
+
}) }),
|
|
655
|
+
/* @__PURE__ */ jsx4(Text4, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs3(Text4, { children: [
|
|
694
656
|
i > 0 ? " " : "",
|
|
695
657
|
/* @__PURE__ */ jsx4(Text4, { color: COLORS.primary, children: key }),
|
|
696
658
|
/* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
|
|
697
659
|
" ",
|
|
698
660
|
label
|
|
699
661
|
] })
|
|
700
|
-
] }, label)) })
|
|
662
|
+
] }, label)) })
|
|
701
663
|
] }) });
|
|
702
664
|
}
|
|
703
665
|
|
|
@@ -730,7 +692,7 @@ function PromptInput() {
|
|
|
730
692
|
}
|
|
731
693
|
if (phase !== "awaitingInput" || !inputReq) return null;
|
|
732
694
|
if (inputReq.promptType === "multipleChoice") {
|
|
733
|
-
return /* @__PURE__ */ jsx5(Box5, {
|
|
695
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
|
|
734
696
|
SelectPrompt,
|
|
735
697
|
{
|
|
736
698
|
question: inputReq.prompt,
|
|
@@ -747,7 +709,7 @@ function PromptInput() {
|
|
|
747
709
|
) });
|
|
748
710
|
}
|
|
749
711
|
if (inputReq.promptType === "multiSelect") {
|
|
750
|
-
return /* @__PURE__ */ jsx5(Box5, {
|
|
712
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
|
|
751
713
|
SelectPrompt,
|
|
752
714
|
{
|
|
753
715
|
multi: true,
|
|
@@ -762,7 +724,7 @@ function PromptInput() {
|
|
|
762
724
|
) });
|
|
763
725
|
}
|
|
764
726
|
if (inputReq.promptType === "notice") {
|
|
765
|
-
return /* @__PURE__ */ jsx5(Box5, {
|
|
727
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
|
|
766
728
|
SelectPrompt,
|
|
767
729
|
{
|
|
768
730
|
question: inputReq.prompt,
|
|
@@ -784,7 +746,7 @@ function PromptInput() {
|
|
|
784
746
|
}
|
|
785
747
|
if (inputReq.promptType === "acceptReject") {
|
|
786
748
|
const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
|
|
787
|
-
return /* @__PURE__ */ jsx5(Box5, {
|
|
749
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
|
|
788
750
|
SelectPrompt,
|
|
789
751
|
{
|
|
790
752
|
question: inputReq.prompt,
|
|
@@ -834,12 +796,12 @@ var sidebarItems = [
|
|
|
834
796
|
description: "push 100 records to Algolia in seconds"
|
|
835
797
|
},
|
|
836
798
|
{
|
|
837
|
-
title: "detect your
|
|
838
|
-
description: "React, Vue, Angular,
|
|
799
|
+
title: "detect your stack",
|
|
800
|
+
description: "React, Vue, Angular, Rails, Django, Laravel & more"
|
|
839
801
|
},
|
|
840
802
|
{
|
|
841
803
|
title: "scaffold a search UI",
|
|
842
|
-
description: "a styled InstantSearch
|
|
804
|
+
description: "a styled InstantSearch UI, wired into your app or templates"
|
|
843
805
|
},
|
|
844
806
|
{
|
|
845
807
|
title: "ship it",
|
|
@@ -945,7 +907,7 @@ var accessItems = [
|
|
|
945
907
|
{
|
|
946
908
|
tag: "READ",
|
|
947
909
|
title: "Project files",
|
|
948
|
-
description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
910
|
+
description: "reads your dependency manifests (package.json\u2026), configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
949
911
|
},
|
|
950
912
|
{
|
|
951
913
|
tag: "WRITE",
|
|
@@ -1565,10 +1527,7 @@ function App() {
|
|
|
1565
1527
|
justifyContent: "space-between",
|
|
1566
1528
|
children: [
|
|
1567
1529
|
showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
|
|
1568
|
-
/* Fill the
|
|
1569
|
-
sidebar, height above the ribbon. The height matters even
|
|
1570
|
-
stacked: it is what the prompt's scrolling list measures itself
|
|
1571
|
-
against (see SelectPrompt). */
|
|
1530
|
+
/* Fill the width beside the sidebar; row layout only (would grow vertically when stacked). */
|
|
1572
1531
|
/* @__PURE__ */ jsxs12(
|
|
1573
1532
|
Box13,
|
|
1574
1533
|
{
|
|
@@ -1576,7 +1535,7 @@ function App() {
|
|
|
1576
1535
|
paddingX: 4,
|
|
1577
1536
|
paddingY: 2,
|
|
1578
1537
|
width: showSidebar ? 70 : "100%",
|
|
1579
|
-
flexGrow: 1,
|
|
1538
|
+
flexGrow: showSidebar ? 1 : 0,
|
|
1580
1539
|
children: [
|
|
1581
1540
|
/* @__PURE__ */ jsx13(Notices, {}),
|
|
1582
1541
|
/* @__PURE__ */ jsx13(PromptInput, {}),
|
|
@@ -2202,15 +2161,282 @@ function writeCredentialsTool(ctx) {
|
|
|
2202
2161
|
// src/lib/tools/searchFiles.ts
|
|
2203
2162
|
import { tool as tool7 } from "ai";
|
|
2204
2163
|
import z10 from "zod";
|
|
2205
|
-
import { readdir as
|
|
2164
|
+
import { readdir as readdir3, readFile as readFile8 } from "node:fs/promises";
|
|
2165
|
+
import { join as join10 } from "node:path";
|
|
2166
|
+
|
|
2167
|
+
// src/lib/languages.ts
|
|
2168
|
+
import { readdir as readdir2, readFile as readFile7 } from "node:fs/promises";
|
|
2169
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
2170
|
+
import { join as join9 } from "node:path";
|
|
2171
|
+
|
|
2172
|
+
// src/lib/tools/utils/packageManager.ts
|
|
2173
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
2174
|
+
import { existsSync } from "node:fs";
|
|
2206
2175
|
import { join as join8 } from "node:path";
|
|
2176
|
+
var LOCKFILES = [
|
|
2177
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
2178
|
+
["yarn.lock", "yarn"],
|
|
2179
|
+
["bun.lockb", "bun"],
|
|
2180
|
+
["bun.lock", "bun"],
|
|
2181
|
+
["package-lock.json", "npm"]
|
|
2182
|
+
];
|
|
2183
|
+
async function readPackageJson(cwd = process.cwd()) {
|
|
2184
|
+
return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
|
|
2185
|
+
}
|
|
2186
|
+
function packageManagerFrom(pkg) {
|
|
2187
|
+
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2188
|
+
}
|
|
2189
|
+
function packageManagerFromLockfile(cwd) {
|
|
2190
|
+
return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
|
|
2191
|
+
}
|
|
2192
|
+
async function detectPackageManager(cwd) {
|
|
2193
|
+
try {
|
|
2194
|
+
const pkg = await readPackageJson(cwd);
|
|
2195
|
+
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2196
|
+
} catch {
|
|
2197
|
+
}
|
|
2198
|
+
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
// src/lib/shell.ts
|
|
2202
|
+
function shellQuote(value) {
|
|
2203
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
// src/lib/languages.ts
|
|
2207
|
+
var ENTRYPOINT_TOKEN = "{entrypoint}";
|
|
2208
|
+
var INGEST_DIR = ".algolia-wizard";
|
|
2209
|
+
var LANGUAGE_PROFILES = {
|
|
2210
|
+
javascript: {
|
|
2211
|
+
id: "javascript",
|
|
2212
|
+
displayName: "JavaScript/TypeScript",
|
|
2213
|
+
aliases: [
|
|
2214
|
+
"javascript",
|
|
2215
|
+
"js",
|
|
2216
|
+
"typescript",
|
|
2217
|
+
"ts",
|
|
2218
|
+
"node",
|
|
2219
|
+
"nodejs",
|
|
2220
|
+
"node.js",
|
|
2221
|
+
"bun",
|
|
2222
|
+
"deno",
|
|
2223
|
+
"ecmascript",
|
|
2224
|
+
"jsx",
|
|
2225
|
+
"tsx"
|
|
2226
|
+
],
|
|
2227
|
+
manifests: ["package.json"],
|
|
2228
|
+
// The concrete npm-family manager is resolved by detectPackageManager (it
|
|
2229
|
+
// honours the package.json `packageManager` field, which lockfiles can't
|
|
2230
|
+
// express), so one spec covers all four and `resolveToolchain` rewrites the
|
|
2231
|
+
// binary below.
|
|
2232
|
+
packageManagers: [
|
|
2233
|
+
{
|
|
2234
|
+
id: "npm",
|
|
2235
|
+
dependency: { mode: "agent-declares", file: "package.json" },
|
|
2236
|
+
installSteps: [{ argv: ["npm", "install"] }],
|
|
2237
|
+
ingest: {
|
|
2238
|
+
kind: "auto",
|
|
2239
|
+
argv: ["node", ENTRYPOINT_TOKEN],
|
|
2240
|
+
entrypointExtensions: [".mjs", ".cjs", ".js"]
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
],
|
|
2244
|
+
sdk: { packageName: "algoliasearch", versionPin: "^5", docKey: "js" },
|
|
2245
|
+
ingestEntrypointExample: `${INGEST_DIR}/ingest.mjs`,
|
|
2246
|
+
// package.json scripts are repo-defined, so they're resolved at run time by
|
|
2247
|
+
// repoVerification rather than listed here.
|
|
2248
|
+
verification: [],
|
|
2249
|
+
envReadInstruction: "Read them from `process.env`.",
|
|
2250
|
+
skipDirs: ["node_modules", "dist", "build", "coverage", ".next", "out"]
|
|
2251
|
+
}
|
|
2252
|
+
};
|
|
2253
|
+
var DEFAULT_LANGUAGE_ID = "javascript";
|
|
2254
|
+
var JAVASCRIPT = "javascript";
|
|
2255
|
+
var CURATED_LANGUAGES = Object.values(
|
|
2256
|
+
LANGUAGE_PROFILES
|
|
2257
|
+
).map((profile) => profile.displayName);
|
|
2258
|
+
function isBackendLanguage(profile) {
|
|
2259
|
+
return profile.id !== JAVASCRIPT;
|
|
2260
|
+
}
|
|
2261
|
+
function normalizeLanguageName(name) {
|
|
2262
|
+
return name.toLowerCase().trim().replace(/[\s_-]+/g, "");
|
|
2263
|
+
}
|
|
2264
|
+
var ALIAS_TO_ID = /* @__PURE__ */ new Map();
|
|
2265
|
+
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
2266
|
+
for (const alias of [profile.id, profile.displayName, ...profile.aliases]) {
|
|
2267
|
+
ALIAS_TO_ID.set(normalizeLanguageName(alias), profile.id);
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
function resolveLanguageProfile(name) {
|
|
2271
|
+
const id = ALIAS_TO_ID.get(normalizeLanguageName(name));
|
|
2272
|
+
return id ? LANGUAGE_PROFILES[id] : void 0;
|
|
2273
|
+
}
|
|
2274
|
+
function isSameLanguage(a, b) {
|
|
2275
|
+
const x = resolveLanguageProfile(a);
|
|
2276
|
+
const y = resolveLanguageProfile(b);
|
|
2277
|
+
if (x && y) return x.id === y.id;
|
|
2278
|
+
if (x || y) return false;
|
|
2279
|
+
const folded = normalizeLanguageName(a);
|
|
2280
|
+
return folded !== "" && folded === normalizeLanguageName(b);
|
|
2281
|
+
}
|
|
2282
|
+
var BASE_SKIP_DIRS = ["node_modules", ".git", "dist"];
|
|
2283
|
+
var ALL_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2284
|
+
...BASE_SKIP_DIRS,
|
|
2285
|
+
...Object.values(LANGUAGE_PROFILES).flatMap((p) => p.skipDirs)
|
|
2286
|
+
]);
|
|
2287
|
+
var ALLOWED_BINARIES = new Set(
|
|
2288
|
+
Object.values(LANGUAGE_PROFILES).flatMap((profile) => [
|
|
2289
|
+
...profile.packageManagers.flatMap((pm) => [
|
|
2290
|
+
...pm.installSteps.map((s) => s.argv[0]),
|
|
2291
|
+
...pm.ingest.kind === "auto" ? [pm.ingest.argv[0]] : []
|
|
2292
|
+
]),
|
|
2293
|
+
...profile.verification.map((v) => v.argv[0])
|
|
2294
|
+
])
|
|
2295
|
+
);
|
|
2296
|
+
var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
|
|
2297
|
+
function isWorktreeRelativeCommand(command) {
|
|
2298
|
+
return command.includes("/");
|
|
2299
|
+
}
|
|
2300
|
+
function withCommand(argv, command) {
|
|
2301
|
+
return [command, ...argv.slice(1)];
|
|
2302
|
+
}
|
|
2303
|
+
function resolveDeclaredManifest(root, packageManager) {
|
|
2304
|
+
const { dependency } = packageManager;
|
|
2305
|
+
if (dependency.mode !== "agent-declares" || !dependency.alternatives?.length) {
|
|
2306
|
+
return packageManager;
|
|
2307
|
+
}
|
|
2308
|
+
const present = [dependency.file, ...dependency.alternatives].find(
|
|
2309
|
+
(file) => existsSync2(join9(root, file))
|
|
2310
|
+
);
|
|
2311
|
+
if (!present || present === dependency.file) return packageManager;
|
|
2312
|
+
return { ...packageManager, dependency: { ...dependency, file: present } };
|
|
2313
|
+
}
|
|
2314
|
+
async function manifestPresent(root, manifest, listing) {
|
|
2315
|
+
if (!manifest.startsWith("*.")) return existsSync2(join9(root, manifest));
|
|
2316
|
+
if (!listing.entries) {
|
|
2317
|
+
const entries = await readdir2(root).catch(() => []);
|
|
2318
|
+
listing.entries = Array.isArray(entries) ? entries : [];
|
|
2319
|
+
}
|
|
2320
|
+
const suffix = manifest.slice(1);
|
|
2321
|
+
return listing.entries.some((e) => e.endsWith(suffix));
|
|
2322
|
+
}
|
|
2323
|
+
async function profileManifestPresent(root, profile, listing) {
|
|
2324
|
+
for (const manifest of profile.manifests) {
|
|
2325
|
+
if (await manifestPresent(root, manifest, listing)) return true;
|
|
2326
|
+
}
|
|
2327
|
+
return false;
|
|
2328
|
+
}
|
|
2329
|
+
async function detectProfilesFromManifests(root) {
|
|
2330
|
+
const listing = {};
|
|
2331
|
+
const found = [];
|
|
2332
|
+
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
2333
|
+
if (await profileManifestPresent(root, profile, listing)) found.push(profile);
|
|
2334
|
+
}
|
|
2335
|
+
return found;
|
|
2336
|
+
}
|
|
2337
|
+
async function hasProfileManifest(root, profile) {
|
|
2338
|
+
return profileManifestPresent(root, profile, {});
|
|
2339
|
+
}
|
|
2340
|
+
async function pickIngestionCandidates(root, confirmedNames) {
|
|
2341
|
+
const confirmed3 = confirmedNames.map((name) => resolveLanguageProfile(name)).filter((p) => p !== void 0);
|
|
2342
|
+
const onDisk = await detectProfilesFromManifests(root);
|
|
2343
|
+
const onDiskIds = new Set(onDisk.map((p) => p.id));
|
|
2344
|
+
const candidates = [
|
|
2345
|
+
...new Map(
|
|
2346
|
+
confirmed3.filter((p) => onDiskIds.has(p.id)).map((p) => [p.id, p])
|
|
2347
|
+
).values()
|
|
2348
|
+
];
|
|
2349
|
+
return { candidates, confirmed: confirmed3, onDisk };
|
|
2350
|
+
}
|
|
2351
|
+
async function resolveToolchain(root, profile) {
|
|
2352
|
+
const signals = (pm) => [
|
|
2353
|
+
...pm.lockfiles ?? [],
|
|
2354
|
+
...pm.detectFiles ?? []
|
|
2355
|
+
];
|
|
2356
|
+
const matched = profile.packageManagers.find(
|
|
2357
|
+
(pm) => signals(pm).some((f) => existsSync2(join9(root, f)))
|
|
2358
|
+
);
|
|
2359
|
+
const fallback = profile.packageManagers.find((pm) => signals(pm).length === 0) ?? profile.packageManagers[0];
|
|
2360
|
+
const packageManager = resolveDeclaredManifest(root, matched ?? fallback);
|
|
2361
|
+
let { installSteps, ingest } = packageManager;
|
|
2362
|
+
installSteps = installSteps.map(
|
|
2363
|
+
(step) => isWorktreeRelativeCommand(step.argv[0]) ? { ...step, argv: withCommand(step.argv, join9(root, step.argv[0])) } : step
|
|
2364
|
+
);
|
|
2365
|
+
if (ingest.kind === "auto" && isWorktreeRelativeCommand(ingest.argv[0])) {
|
|
2366
|
+
ingest = {
|
|
2367
|
+
...ingest,
|
|
2368
|
+
argv: withCommand(ingest.argv, join9(root, ingest.argv[0]))
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
if (profile.id === "javascript") {
|
|
2372
|
+
const pm = await detectPackageManager(root);
|
|
2373
|
+
if (JS_PACKAGE_MANAGERS.has(pm)) {
|
|
2374
|
+
installSteps = installSteps.map((step) => ({
|
|
2375
|
+
...step,
|
|
2376
|
+
argv: withCommand(step.argv, pm)
|
|
2377
|
+
}));
|
|
2378
|
+
if (pm === "bun" && ingest.kind === "auto") {
|
|
2379
|
+
ingest = { ...ingest, argv: withCommand(ingest.argv, "bun") };
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
return { profile, packageManager, installSteps, ingest };
|
|
2384
|
+
}
|
|
2385
|
+
function resolveIngestArgv(ingest, entrypoint) {
|
|
2386
|
+
if (ingest.kind !== "auto") {
|
|
2387
|
+
throw new Error("resolveIngestArgv called for a manual-run toolchain");
|
|
2388
|
+
}
|
|
2389
|
+
return ingest.argv.map(
|
|
2390
|
+
(part) => part === ENTRYPOINT_TOKEN ? entrypoint : part
|
|
2391
|
+
);
|
|
2392
|
+
}
|
|
2393
|
+
function describeIngestCommand(ingest, entrypoint) {
|
|
2394
|
+
if (ingest.kind !== "auto") return ingest.runCommand;
|
|
2395
|
+
return ingest.argv.map((part) => part === ENTRYPOINT_TOKEN ? shellQuote(entrypoint) : part).join(" ");
|
|
2396
|
+
}
|
|
2397
|
+
function ingestScriptDir(profile) {
|
|
2398
|
+
const parts = profile.ingestEntrypointExample.split("/");
|
|
2399
|
+
return parts.slice(0, -1).join("/") || ".";
|
|
2400
|
+
}
|
|
2401
|
+
function localSourceLimitation(root, profile) {
|
|
2402
|
+
const caveat = profile.localSourceCaveat;
|
|
2403
|
+
if (!caveat) return void 0;
|
|
2404
|
+
return existsSync2(join9(root, caveat.unless)) ? void 0 : caveat.message;
|
|
2405
|
+
}
|
|
2406
|
+
async function missingBuildTask(root, toolchain) {
|
|
2407
|
+
const { ingest, packageManager } = toolchain;
|
|
2408
|
+
if (ingest.kind !== "manual" || !ingest.requiresBuildTask) return void 0;
|
|
2409
|
+
if (packageManager.dependency.mode !== "agent-declares") return void 0;
|
|
2410
|
+
const buildFile = join9(root, packageManager.dependency.file);
|
|
2411
|
+
const contents = await readFile7(buildFile, "utf8").catch(() => void 0);
|
|
2412
|
+
if (contents === void 0) return void 0;
|
|
2413
|
+
return contents.includes(ingest.requiresBuildTask) ? void 0 : ingest.requiresBuildTask;
|
|
2414
|
+
}
|
|
2415
|
+
function sdkVersionPin(profile, packageManager) {
|
|
2416
|
+
return packageManager.sdkVersionPin ?? profile.sdk.versionPin;
|
|
2417
|
+
}
|
|
2418
|
+
function dependencyInstruction(toolchain) {
|
|
2419
|
+
const { profile, packageManager } = toolchain;
|
|
2420
|
+
const { packageName } = profile.sdk;
|
|
2421
|
+
const versionPin = sdkVersionPin(profile, packageManager);
|
|
2422
|
+
const also = profile.sdk.alsoRequires ? ` ${profile.sdk.alsoRequires}` : "";
|
|
2423
|
+
switch (packageManager.dependency.mode) {
|
|
2424
|
+
case "wizard-installs":
|
|
2425
|
+
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}`;
|
|
2426
|
+
case "code-imports":
|
|
2427
|
+
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}`;
|
|
2428
|
+
case "agent-declares":
|
|
2429
|
+
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}`;
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
// src/lib/tools/searchFiles.ts
|
|
2207
2434
|
var MAX_QUERY_LENGTH = 1e3;
|
|
2208
2435
|
async function walkFiles(dir) {
|
|
2209
|
-
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
2210
2436
|
const out = [];
|
|
2211
|
-
for (const e of await
|
|
2212
|
-
if (e.name.startsWith(".") ||
|
|
2213
|
-
const full =
|
|
2437
|
+
for (const e of await readdir3(dir, { withFileTypes: true })) {
|
|
2438
|
+
if (e.name.startsWith(".") || ALL_SKIP_DIRS.has(e.name)) continue;
|
|
2439
|
+
const full = join10(dir, e.name);
|
|
2214
2440
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2215
2441
|
else if (e.isFile()) out.push(full);
|
|
2216
2442
|
}
|
|
@@ -2243,7 +2469,7 @@ function searchFilesTool(ctx) {
|
|
|
2243
2469
|
for (const file of await walkFiles(resolved.target)) {
|
|
2244
2470
|
let content;
|
|
2245
2471
|
try {
|
|
2246
|
-
content = await
|
|
2472
|
+
content = await readFile8(file, "utf8");
|
|
2247
2473
|
} catch {
|
|
2248
2474
|
continue;
|
|
2249
2475
|
}
|
|
@@ -2267,88 +2493,144 @@ function searchFilesTool(ctx) {
|
|
|
2267
2493
|
import { tool as tool8 } from "ai";
|
|
2268
2494
|
import z11 from "zod";
|
|
2269
2495
|
|
|
2496
|
+
// src/lib/tools/repoVerification.ts
|
|
2497
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
2498
|
+
import { join as join11 } from "node:path";
|
|
2499
|
+
|
|
2270
2500
|
// src/lib/tools/utils/runCommand.ts
|
|
2271
2501
|
import { spawn as spawn2 } from "node:child_process";
|
|
2272
|
-
|
|
2502
|
+
var INSTALL_TIMEOUT_MS = 15 * 6e4;
|
|
2503
|
+
var INGEST_TIMEOUT_MS = 15 * 6e4;
|
|
2504
|
+
var VERIFY_TIMEOUT_MS = 10 * 6e4;
|
|
2505
|
+
var KILL_GRACE_MS = 5e3;
|
|
2506
|
+
function runCommand(command, args, options = {}) {
|
|
2507
|
+
const { cwd, env, timeoutMs = VERIFY_TIMEOUT_MS } = options;
|
|
2273
2508
|
return new Promise((resolve4) => {
|
|
2274
2509
|
let output = "";
|
|
2510
|
+
let settled = false;
|
|
2275
2511
|
const child = spawn2(command, args, {
|
|
2276
2512
|
cwd,
|
|
2277
|
-
|
|
2513
|
+
shell: false,
|
|
2514
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2515
|
+
...env ? { env: { ...process.env, ...env } } : {}
|
|
2278
2516
|
});
|
|
2517
|
+
const settle = (result) => {
|
|
2518
|
+
if (settled) return;
|
|
2519
|
+
settled = true;
|
|
2520
|
+
clearTimeout(timer);
|
|
2521
|
+
resolve4(result);
|
|
2522
|
+
};
|
|
2523
|
+
const timer = setTimeout(() => {
|
|
2524
|
+
child.kill("SIGTERM");
|
|
2525
|
+
setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
|
|
2526
|
+
const seconds = Math.round(timeoutMs / 1e3);
|
|
2527
|
+
settle({
|
|
2528
|
+
code: 1,
|
|
2529
|
+
output: `${output}
|
|
2530
|
+
Timed out after ${seconds}s: ${command} ${args.join(" ")}`.trim(),
|
|
2531
|
+
timedOut: true
|
|
2532
|
+
});
|
|
2533
|
+
}, timeoutMs);
|
|
2279
2534
|
child.stdout?.on("data", (d) => output += d);
|
|
2280
2535
|
child.stderr?.on("data", (d) => output += d);
|
|
2281
2536
|
child.on(
|
|
2282
2537
|
"error",
|
|
2283
|
-
(err) =>
|
|
2538
|
+
(err) => settle({
|
|
2539
|
+
code: 1,
|
|
2540
|
+
output: `Failed to run ${command}: ${err.message}`,
|
|
2541
|
+
timedOut: false
|
|
2542
|
+
})
|
|
2543
|
+
);
|
|
2544
|
+
child.on(
|
|
2545
|
+
"close",
|
|
2546
|
+
(code) => settle({ code: code ?? 1, output, timedOut: false })
|
|
2284
2547
|
);
|
|
2285
|
-
child.on("close", (code) => resolve4({ code: code ?? 1, output }));
|
|
2286
2548
|
});
|
|
2287
2549
|
}
|
|
2288
2550
|
|
|
2289
|
-
// src/lib/tools/utils/packageManager.ts
|
|
2290
|
-
import { readFile as readFile7 } from "node:fs/promises";
|
|
2291
|
-
import { existsSync } from "node:fs";
|
|
2292
|
-
import { join as join9 } from "node:path";
|
|
2293
|
-
var LOCKFILES = [
|
|
2294
|
-
["pnpm-lock.yaml", "pnpm"],
|
|
2295
|
-
["yarn.lock", "yarn"],
|
|
2296
|
-
["bun.lockb", "bun"],
|
|
2297
|
-
["bun.lock", "bun"],
|
|
2298
|
-
["package-lock.json", "npm"]
|
|
2299
|
-
];
|
|
2300
|
-
async function readPackageJson(cwd = process.cwd()) {
|
|
2301
|
-
return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
|
|
2302
|
-
}
|
|
2303
|
-
function packageManagerFrom(pkg) {
|
|
2304
|
-
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2305
|
-
}
|
|
2306
|
-
function packageManagerFromLockfile(cwd) {
|
|
2307
|
-
return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
|
|
2308
|
-
}
|
|
2309
|
-
async function detectPackageManager(cwd) {
|
|
2310
|
-
try {
|
|
2311
|
-
const pkg = await readPackageJson(cwd);
|
|
2312
|
-
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2313
|
-
} catch {
|
|
2314
|
-
}
|
|
2315
|
-
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2316
|
-
}
|
|
2317
|
-
|
|
2318
2551
|
// src/lib/tools/repoVerification.ts
|
|
2319
2552
|
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
2320
|
-
async function
|
|
2553
|
+
async function runCheck(command, binary, args) {
|
|
2554
|
+
const { code, output } = await runCommand(binary, args, {
|
|
2555
|
+
timeoutMs: VERIFY_TIMEOUT_MS
|
|
2556
|
+
});
|
|
2557
|
+
return { command, exitCode: code, ok: code === 0, output: output.trim() };
|
|
2558
|
+
}
|
|
2559
|
+
async function javascriptChecks() {
|
|
2321
2560
|
let pkg;
|
|
2322
2561
|
try {
|
|
2323
2562
|
pkg = await readPackageJson();
|
|
2324
2563
|
} catch (err) {
|
|
2325
|
-
|
|
2326
|
-
|
|
2564
|
+
return {
|
|
2565
|
+
limitation: `Could not read package.json to detect verification conventions: ${err.message}`
|
|
2566
|
+
};
|
|
2327
2567
|
}
|
|
2328
2568
|
const scripts = pkg.scripts ?? {};
|
|
2329
2569
|
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
2330
2570
|
if (present.length === 0) {
|
|
2331
|
-
|
|
2332
|
-
|
|
2571
|
+
return {
|
|
2572
|
+
limitation: `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`
|
|
2573
|
+
};
|
|
2333
2574
|
}
|
|
2334
2575
|
const pm = await detectPackageManager(process.cwd());
|
|
2335
2576
|
const checks = [];
|
|
2336
2577
|
for (const script of present) {
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2578
|
+
checks.push(
|
|
2579
|
+
await runCheck(`${pm} run ${script}`, pm, ["run", script])
|
|
2580
|
+
);
|
|
2581
|
+
}
|
|
2582
|
+
return { checks };
|
|
2583
|
+
}
|
|
2584
|
+
async function registryChecks(id) {
|
|
2585
|
+
const profile = LANGUAGE_PROFILES[id];
|
|
2586
|
+
const runnable = profile.verification.filter(
|
|
2587
|
+
(spec) => !spec.requiresFile || existsSync3(join11(process.cwd(), spec.requiresFile))
|
|
2588
|
+
);
|
|
2589
|
+
if (runnable.length === 0) {
|
|
2590
|
+
return {
|
|
2591
|
+
limitation: `No mechanical verification available for ${profile.displayName} in this repo.`
|
|
2592
|
+
};
|
|
2593
|
+
}
|
|
2594
|
+
const checks = [];
|
|
2595
|
+
for (const spec of runnable) {
|
|
2596
|
+
checks.push(
|
|
2597
|
+
await runCheck(spec.argv.join(" "), spec.argv[0], [...spec.argv.slice(1)])
|
|
2598
|
+
);
|
|
2340
2599
|
}
|
|
2341
|
-
return {
|
|
2600
|
+
return { checks };
|
|
2601
|
+
}
|
|
2602
|
+
async function runRepoVerificationCheck(languages = [DEFAULT_LANGUAGE_ID]) {
|
|
2603
|
+
const ids = [...new Set(languages)];
|
|
2604
|
+
if (ids.length === 0) ids.push(DEFAULT_LANGUAGE_ID);
|
|
2605
|
+
const checks = [];
|
|
2606
|
+
const limitations = [];
|
|
2607
|
+
for (const id of ids) {
|
|
2608
|
+
const result = id === JAVASCRIPT ? await javascriptChecks() : await registryChecks(id);
|
|
2609
|
+
if ("checks" in result) checks.push(...result.checks);
|
|
2610
|
+
else limitations.push(result.limitation);
|
|
2611
|
+
}
|
|
2612
|
+
if (checks.length === 0) {
|
|
2613
|
+
return {
|
|
2614
|
+
ok: false,
|
|
2615
|
+
checks: [],
|
|
2616
|
+
limitation: limitations.join(" ") || "No verification checks available."
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
return {
|
|
2620
|
+
ok: checks.every((c) => c.ok),
|
|
2621
|
+
checks,
|
|
2622
|
+
...limitations.length ? { limitation: limitations.join(" ") } : {}
|
|
2623
|
+
};
|
|
2342
2624
|
}
|
|
2343
2625
|
|
|
2344
2626
|
// src/lib/tools/verifyImplementation.ts
|
|
2345
|
-
function verifyImplementationTool() {
|
|
2627
|
+
function verifyImplementationTool(ctx) {
|
|
2346
2628
|
return tool8({
|
|
2347
|
-
description: "Run the repo's mechanical verification
|
|
2629
|
+
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.",
|
|
2348
2630
|
inputSchema: z11.object(),
|
|
2349
2631
|
execute: async () => {
|
|
2350
|
-
logger.info("called verifyImplementation tool");
|
|
2351
|
-
return runRepoVerificationCheck();
|
|
2632
|
+
logger.info({ languages: ctx.languages }, "called verifyImplementation tool");
|
|
2633
|
+
return runRepoVerificationCheck(ctx.languages);
|
|
2352
2634
|
}
|
|
2353
2635
|
});
|
|
2354
2636
|
}
|
|
@@ -2474,12 +2756,17 @@ var DEFAULT_TOOL_LIMITS = {
|
|
|
2474
2756
|
read: 20,
|
|
2475
2757
|
match: 100
|
|
2476
2758
|
};
|
|
2477
|
-
function createToolContext(
|
|
2759
|
+
function createToolContext({
|
|
2760
|
+
limits = DEFAULT_TOOL_LIMITS,
|
|
2761
|
+
cwd = process.cwd(),
|
|
2762
|
+
languages = [DEFAULT_LANGUAGE_ID]
|
|
2763
|
+
} = {}) {
|
|
2478
2764
|
return {
|
|
2479
2765
|
root: cwd,
|
|
2480
2766
|
cwd,
|
|
2481
2767
|
limits,
|
|
2482
|
-
counts: { list: 0, search: 0, read: 0 }
|
|
2768
|
+
counts: { list: 0, search: 0, read: 0 },
|
|
2769
|
+
languages: languages.length ? languages : [DEFAULT_LANGUAGE_ID]
|
|
2483
2770
|
};
|
|
2484
2771
|
}
|
|
2485
2772
|
|
|
@@ -2516,7 +2803,7 @@ function createTools(ctx, { output, tools }) {
|
|
|
2516
2803
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
2517
2804
|
verifyImplementation: withLogging(
|
|
2518
2805
|
"verifyImplementation",
|
|
2519
|
-
verifyImplementationTool()
|
|
2806
|
+
verifyImplementationTool(ctx)
|
|
2520
2807
|
),
|
|
2521
2808
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
2522
2809
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
@@ -2552,7 +2839,7 @@ async function runAgent(req) {
|
|
|
2552
2839
|
baseURL: PROXY_BASE_URL,
|
|
2553
2840
|
fetch: proxyFetch
|
|
2554
2841
|
});
|
|
2555
|
-
const toolContext = createToolContext();
|
|
2842
|
+
const toolContext = createToolContext({ languages: req.languages });
|
|
2556
2843
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
2557
2844
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
2558
2845
|
const instructions = [
|
|
@@ -2643,8 +2930,11 @@ var detectLanguageSchema = z16.object({
|
|
|
2643
2930
|
var detectLanguage = () => runAgent({
|
|
2644
2931
|
instructions: [
|
|
2645
2932
|
"Analyze the codebase and determine the programming languages and frameworks used",
|
|
2933
|
+
"Start from the dependency manifests: package.json.",
|
|
2934
|
+
"List the language that owns the backend/data code first \u2014 that is the one an ingestion script will be written in.",
|
|
2646
2935
|
"If a superset language is found, exclude the subset language. TS-over-JS.",
|
|
2647
2936
|
"If a meta-framework is used, exclude the framework. Next-over-React.",
|
|
2937
|
+
"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).",
|
|
2648
2938
|
"Return the exact version",
|
|
2649
2939
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
2650
2940
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
@@ -2693,6 +2983,7 @@ var MODE_CONFIG = {
|
|
|
2693
2983
|
"Analyze the codebase to find the data entities (models) that should be ingested into Algolia.",
|
|
2694
2984
|
"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).",
|
|
2695
2985
|
"Inspect the source of each entity to extract real field names for attributes \u2014 do not guess or leave attributes empty.",
|
|
2986
|
+
"Entities live wherever the stack keeps them: TypeScript interfaces or a Prisma schema.",
|
|
2696
2987
|
"Prefer domain models (e.g. Document, Product, User) over framework or infrastructure types.",
|
|
2697
2988
|
"Use as few tools as possible, but do not guess. If you cannot find any entities, return an empty array.",
|
|
2698
2989
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
@@ -2704,8 +2995,9 @@ var MODE_CONFIG = {
|
|
|
2704
2995
|
instructions: [
|
|
2705
2996
|
"Analyze the codebase to determine the single best location to add search UI functionality.",
|
|
2706
2997
|
"Prefer a shared, always-rendered layout location (e.g. a header or navigation component) so search is reachable across the app.",
|
|
2707
|
-
"
|
|
2708
|
-
|
|
2998
|
+
"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).",
|
|
2999
|
+
"Return one file path as searchImplementationAnalysis.",
|
|
3000
|
+
'Use as few tools as possible, but do not guess. If the project renders no UI at all (an API-only service), say "unknown".',
|
|
2709
3001
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
2710
3002
|
"When done, call reportStatus"
|
|
2711
3003
|
],
|
|
@@ -2714,7 +3006,7 @@ var MODE_CONFIG = {
|
|
|
2714
3006
|
verification: {
|
|
2715
3007
|
instructions: [
|
|
2716
3008
|
"Analyze the codebase to determine which code-quality tools are available to validate changes.",
|
|
2717
|
-
"Look at
|
|
3009
|
+
"Look at the dependency manifest and config files for the project's languages: package.json scripts with tsconfig/eslint/prettier.",
|
|
2718
3010
|
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"].',
|
|
2719
3011
|
"Use as few tools as possible, but do not guess. If you cannot find any, return an empty array.",
|
|
2720
3012
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
@@ -2742,7 +3034,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
2742
3034
|
// package.json
|
|
2743
3035
|
var package_default = {
|
|
2744
3036
|
name: "@algolia/wizard",
|
|
2745
|
-
version: "0.8.0-rc.
|
|
3037
|
+
version: "0.8.0-rc.59.47",
|
|
2746
3038
|
description: "Magically implement Algolia functionality in your codebase",
|
|
2747
3039
|
type: "module",
|
|
2748
3040
|
engines: {
|
|
@@ -2764,7 +3056,7 @@ var package_default = {
|
|
|
2764
3056
|
prepare: "husky",
|
|
2765
3057
|
prepublishOnly: "pnpm build",
|
|
2766
3058
|
reset: "tsx ./scripts/reset-state.ts",
|
|
2767
|
-
"test:
|
|
3059
|
+
"test:toolchains": "tsx ./scripts/verify-toolchains.ts",
|
|
2768
3060
|
"test:tools": "tsx ./tool-evals/toolEval.ts",
|
|
2769
3061
|
test: "vitest",
|
|
2770
3062
|
typecheck: "tsc --noEmit -p tsconfig.json"
|
|
@@ -2845,82 +3137,185 @@ function parseEntries(raw) {
|
|
|
2845
3137
|
return raw.split(",").map(clean).filter(Boolean).slice(0, MAX_ENTRIES).map((name) => ({ name, version: "unknown" }));
|
|
2846
3138
|
}
|
|
2847
3139
|
var summarize = (entries) => entries.length ? entries.map((e) => e.name).join(", ") : "none";
|
|
2848
|
-
|
|
3140
|
+
|
|
3141
|
+
// src/actions/confirmLanguage.ts
|
|
3142
|
+
import z19 from "zod";
|
|
3143
|
+
var confirmLanguageSchema = z19.object({
|
|
3144
|
+
languages: detectLanguageSchema.shape.languages
|
|
3145
|
+
});
|
|
3146
|
+
var OTHER_OPTION = "Other";
|
|
3147
|
+
function confirmed(languages) {
|
|
3148
|
+
track("AI Wizard Language Confirmed", { languages });
|
|
3149
|
+
return { languages };
|
|
3150
|
+
}
|
|
3151
|
+
async function askOtherLanguage(ctx) {
|
|
3152
|
+
let prompt = "enter the language for your ingestion script";
|
|
2849
3153
|
for (; ; ) {
|
|
2850
3154
|
const answer = await ctx.requestUserInput({
|
|
2851
3155
|
prompt,
|
|
2852
3156
|
promptType: "textInput",
|
|
2853
|
-
options: []
|
|
2854
|
-
helpText: 'Comma-separated, e.g. "TypeScript, Node".'
|
|
3157
|
+
options: []
|
|
2855
3158
|
});
|
|
2856
3159
|
if (typeof answer !== "string") {
|
|
2857
|
-
throw new Error("
|
|
3160
|
+
throw new Error("confirmLanguage received an unexpected non-text result");
|
|
2858
3161
|
}
|
|
2859
|
-
const
|
|
2860
|
-
if (
|
|
2861
|
-
prompt = "
|
|
3162
|
+
const name = parseEntries(answer)[0]?.name;
|
|
3163
|
+
if (name) return name;
|
|
3164
|
+
prompt = "please enter a language name:";
|
|
2862
3165
|
}
|
|
2863
3166
|
}
|
|
2864
|
-
|
|
2865
|
-
// src/actions/confirmLanguage.ts
|
|
2866
|
-
import z19 from "zod";
|
|
2867
|
-
var confirmLanguageSchema = z19.object({
|
|
2868
|
-
languages: detectLanguageSchema.shape.languages
|
|
2869
|
-
});
|
|
2870
3167
|
async function confirmLanguage(ctx) {
|
|
2871
3168
|
const detected = ctx.getStepOutput("project-scan");
|
|
2872
|
-
const
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
3169
|
+
const detectedLanguages = detected.languages ?? [];
|
|
3170
|
+
const others = (chosen) => detectedLanguages.filter((l) => !isSameLanguage(l.name, chosen));
|
|
3171
|
+
const primary = detectedLanguages[0];
|
|
3172
|
+
if (primary) {
|
|
3173
|
+
const accepted = await ctx.requestUserInput({
|
|
3174
|
+
prompt: `Write the ingestion script in ${primary.name}?`,
|
|
3175
|
+
promptType: "acceptReject",
|
|
3176
|
+
options: [`Confirm ${primary.name}`, "Use a different language"],
|
|
3177
|
+
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0],
|
|
3178
|
+
messages: detectedLanguages.length > 1 ? [`Detected: ${summarize(detectedLanguages)}`] : []
|
|
3179
|
+
});
|
|
3180
|
+
if (accepted === true) return confirmed(detectedLanguages);
|
|
3181
|
+
}
|
|
3182
|
+
const options = [...CURATED_LANGUAGES];
|
|
3183
|
+
for (const language of detectedLanguages) {
|
|
3184
|
+
if (!options.some((o) => isSameLanguage(o, language.name))) {
|
|
3185
|
+
options.push(language.name);
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
options.push(OTHER_OPTION);
|
|
3189
|
+
const detectedFor = (option) => detectedLanguages.find((l) => isSameLanguage(option, l.name));
|
|
3190
|
+
const secondary = options.map(
|
|
3191
|
+
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
3192
|
+
);
|
|
3193
|
+
const defaultSelectedIndex = Math.max(
|
|
3194
|
+
options.findIndex((o) => detectedFor(o)),
|
|
3195
|
+
0
|
|
3196
|
+
);
|
|
3197
|
+
const selection = await ctx.requestUserInput({
|
|
3198
|
+
prompt: "select the language for your ingestion script",
|
|
3199
|
+
promptType: "multipleChoice",
|
|
3200
|
+
options,
|
|
3201
|
+
secondary,
|
|
3202
|
+
defaultSelectedIndex
|
|
2883
3203
|
});
|
|
2884
|
-
|
|
3204
|
+
if (typeof selection !== "string") {
|
|
3205
|
+
throw new Error("confirmLanguage received an unexpected non-text result");
|
|
3206
|
+
}
|
|
3207
|
+
const name = selection === OTHER_OPTION ? await askOtherLanguage(ctx) : selection;
|
|
3208
|
+
const version = detectedFor(name)?.version ?? "unknown";
|
|
3209
|
+
return confirmed([{ name, version }, ...others(name)]);
|
|
2885
3210
|
}
|
|
2886
3211
|
|
|
2887
3212
|
// src/actions/confirmFramework.ts
|
|
2888
3213
|
import z20 from "zod";
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
var
|
|
2893
|
-
|
|
2894
|
-
"
|
|
2895
|
-
"
|
|
2896
|
-
"
|
|
2897
|
-
"
|
|
2898
|
-
|
|
3214
|
+
|
|
3215
|
+
// src/lib/frameworks.ts
|
|
3216
|
+
var BACKEND_ONLY_FRAMEWORK = "Backend only / API";
|
|
3217
|
+
var FRAMEWORKS = [
|
|
3218
|
+
// Frontend — InstantSearch component flavors.
|
|
3219
|
+
{ name: "Next.js", strategy: "react", aliases: ["next", "nextjs"] },
|
|
3220
|
+
{ name: "React", strategy: "react", aliases: ["reactjs"] },
|
|
3221
|
+
{ name: "Vue", strategy: "vue", aliases: ["vuejs", "nuxt", "nuxtjs"] },
|
|
3222
|
+
{ name: "Angular", strategy: "angular", aliases: ["angularjs"] },
|
|
3223
|
+
// No Svelte InstantSearch flavor exists, so it uses InstantSearch.js.
|
|
3224
|
+
{ name: "Svelte", strategy: "js", aliases: ["sveltekit"] },
|
|
3225
|
+
{
|
|
3226
|
+
name: "Vanilla JS",
|
|
3227
|
+
strategy: "js",
|
|
3228
|
+
aliases: ["vanilla", "javascript", "js", "astro", "vite"]
|
|
3229
|
+
},
|
|
3230
|
+
// Backend — Algolia's official framework integrations. Server-rendered
|
|
3231
|
+
// templates get InstantSearch.js from a CDN.
|
|
3232
|
+
{
|
|
3233
|
+
name: "Rails",
|
|
3234
|
+
strategy: "cdn-template",
|
|
3235
|
+
aliases: ["rubyonrails", "ruby on rails", "erb"]
|
|
3236
|
+
},
|
|
3237
|
+
{ name: "Django", strategy: "cdn-template", aliases: ["jinja", "jinja2"] },
|
|
3238
|
+
{ name: "Laravel", strategy: "cdn-template", aliases: ["blade"] },
|
|
3239
|
+
{ name: "Symfony", strategy: "cdn-template", aliases: ["twig"] },
|
|
3240
|
+
// Mobile — Algolia ships InstantSearch iOS/Android and Dart clients, but the
|
|
3241
|
+
// wizard can't scaffold a native UI, so it points at the docs instead.
|
|
3242
|
+
{ name: "Flutter", strategy: "none", aliases: [] },
|
|
3243
|
+
{ name: "iOS", strategy: "none", aliases: ["swiftui", "uikit"] },
|
|
3244
|
+
{ name: "Android", strategy: "none", aliases: ["jetpack compose", "compose"] },
|
|
3245
|
+
{ name: BACKEND_ONLY_FRAMEWORK, strategy: "cdn-template", aliases: [] }
|
|
2899
3246
|
];
|
|
2900
|
-
var
|
|
3247
|
+
var CURATED_FRAMEWORKS = FRAMEWORKS.map(
|
|
3248
|
+
(f) => f.name
|
|
3249
|
+
);
|
|
2901
3250
|
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2902
|
-
var
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
javascript: "vanillajs",
|
|
2916
|
-
js: "vanillajs"
|
|
2917
|
-
};
|
|
2918
|
-
var isSameFramework = (a, b) => {
|
|
2919
|
-
const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
|
|
2920
|
-
const y = FRAMEWORK_ALIASES[normalize(b)] ?? normalize(b);
|
|
3251
|
+
var ALIAS_TO_NAME = /* @__PURE__ */ new Map();
|
|
3252
|
+
for (const framework of FRAMEWORKS) {
|
|
3253
|
+
for (const alias of [framework.name, ...framework.aliases]) {
|
|
3254
|
+
ALIAS_TO_NAME.set(normalize(alias), framework.name);
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
var STRATEGY_BY_NAME = new Map(FRAMEWORKS.map((f) => [f.name, f.strategy]));
|
|
3258
|
+
function canonicalFrameworkName(name) {
|
|
3259
|
+
return ALIAS_TO_NAME.get(normalize(name));
|
|
3260
|
+
}
|
|
3261
|
+
function isSameFramework(a, b) {
|
|
3262
|
+
const x = canonicalFrameworkName(a) ?? normalize(a);
|
|
3263
|
+
const y = canonicalFrameworkName(b) ?? normalize(b);
|
|
2921
3264
|
return x !== "" && x === y;
|
|
2922
|
-
}
|
|
2923
|
-
function
|
|
3265
|
+
}
|
|
3266
|
+
function resolveSearchStrategy(frameworkName, hasJavaScriptInStack) {
|
|
3267
|
+
const canonical = frameworkName ? canonicalFrameworkName(frameworkName) : void 0;
|
|
3268
|
+
const strategy = canonical ? STRATEGY_BY_NAME.get(canonical) : void 0;
|
|
3269
|
+
if (strategy) return strategy;
|
|
3270
|
+
return hasJavaScriptInStack ? "js" : "cdn-template";
|
|
3271
|
+
}
|
|
3272
|
+
function searchDocKey(strategy) {
|
|
3273
|
+
return strategy === "cdn-template" ? "templates" : strategy;
|
|
3274
|
+
}
|
|
3275
|
+
function bundlesJavaScript(strategy) {
|
|
3276
|
+
return strategy !== "cdn-template" && strategy !== "none";
|
|
3277
|
+
}
|
|
3278
|
+
function canScaffoldSearchUI(strategy) {
|
|
3279
|
+
return strategy !== "none";
|
|
3280
|
+
}
|
|
3281
|
+
var ENV_PREFIXES = [
|
|
3282
|
+
{ aliases: ["next", "nextjs"], prefix: "NEXT_PUBLIC_" },
|
|
3283
|
+
{ aliases: ["nuxt", "nuxtjs"], prefix: "NUXT_PUBLIC_" },
|
|
3284
|
+
{ aliases: ["astro"], prefix: "PUBLIC_" },
|
|
3285
|
+
{ aliases: ["vite"], prefix: "VITE_" }
|
|
3286
|
+
];
|
|
3287
|
+
var DEFAULT_ENV_PREFIX = "PUBLIC_";
|
|
3288
|
+
function publicEnvPrefix(frameworkNames, strategy) {
|
|
3289
|
+
if (!bundlesJavaScript(strategy)) return "";
|
|
3290
|
+
const present = new Set(frameworkNames.map(normalize));
|
|
3291
|
+
for (const { aliases, prefix } of ENV_PREFIXES) {
|
|
3292
|
+
if (aliases.some((alias) => present.has(alias))) return prefix;
|
|
3293
|
+
}
|
|
3294
|
+
return DEFAULT_ENV_PREFIX;
|
|
3295
|
+
}
|
|
3296
|
+
function describeSearchTarget(strategy, frameworkName) {
|
|
3297
|
+
switch (strategy) {
|
|
3298
|
+
case "react":
|
|
3299
|
+
return "React (react-instantsearch)";
|
|
3300
|
+
case "vue":
|
|
3301
|
+
return "Vue (vue-instantsearch)";
|
|
3302
|
+
case "angular":
|
|
3303
|
+
return "Angular (angular-instantsearch)";
|
|
3304
|
+
case "js":
|
|
3305
|
+
return "plain JavaScript (InstantSearch.js)";
|
|
3306
|
+
case "cdn-template":
|
|
3307
|
+
return `${frameworkName ?? "server-rendered"} templates (InstantSearch.js via CDN)`;
|
|
3308
|
+
case "none":
|
|
3309
|
+
return frameworkName ?? "a native mobile app";
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
// src/actions/confirmFramework.ts
|
|
3314
|
+
var confirmFrameworkSchema = z20.object({
|
|
3315
|
+
frameworks: detectLanguageSchema.shape.frameworks
|
|
3316
|
+
});
|
|
3317
|
+
var OTHER_OPTION2 = "Other";
|
|
3318
|
+
function confirmed2(name, version) {
|
|
2924
3319
|
const frameworks = [{ name, version: version ?? "unknown" }];
|
|
2925
3320
|
track("AI Wizard Frontend Framework Confirmed", { frameworks });
|
|
2926
3321
|
return { frameworks };
|
|
@@ -2948,7 +3343,7 @@ async function confirmFramework(ctx) {
|
|
|
2948
3343
|
for (const fw of detectedFrameworks) {
|
|
2949
3344
|
if (!options.some((o) => isSameFramework(o, fw.name))) options.push(fw.name);
|
|
2950
3345
|
}
|
|
2951
|
-
options.push(
|
|
3346
|
+
options.push(OTHER_OPTION2);
|
|
2952
3347
|
const detectedFor = (option) => detectedFrameworks.find((fw) => isSameFramework(option, fw.name));
|
|
2953
3348
|
const primary = detectedFrameworks[0];
|
|
2954
3349
|
if (primary) {
|
|
@@ -2958,7 +3353,7 @@ async function confirmFramework(ctx) {
|
|
|
2958
3353
|
options: [`Confirm ${primary.name}`, "Use a different framework"],
|
|
2959
3354
|
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0]
|
|
2960
3355
|
});
|
|
2961
|
-
if (accepted === true) return
|
|
3356
|
+
if (accepted === true) return confirmed2(primary.name, primary.version);
|
|
2962
3357
|
}
|
|
2963
3358
|
const secondary = options.map(
|
|
2964
3359
|
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
@@ -2968,7 +3363,7 @@ async function confirmFramework(ctx) {
|
|
|
2968
3363
|
0
|
|
2969
3364
|
);
|
|
2970
3365
|
const selection = await ctx.requestUserInput({
|
|
2971
|
-
prompt: "select
|
|
3366
|
+
prompt: "select the framework that renders your UI",
|
|
2972
3367
|
promptType: "multipleChoice",
|
|
2973
3368
|
options,
|
|
2974
3369
|
secondary,
|
|
@@ -2977,10 +3372,10 @@ async function confirmFramework(ctx) {
|
|
|
2977
3372
|
if (typeof selection !== "string") {
|
|
2978
3373
|
throw new Error("confirmFramework received an unexpected non-text result");
|
|
2979
3374
|
}
|
|
2980
|
-
if (selection ===
|
|
2981
|
-
return
|
|
3375
|
+
if (selection === OTHER_OPTION2) {
|
|
3376
|
+
return confirmed2(await askOtherFramework(ctx));
|
|
2982
3377
|
}
|
|
2983
|
-
return
|
|
3378
|
+
return confirmed2(selection, detectedFor(selection)?.version);
|
|
2984
3379
|
}
|
|
2985
3380
|
|
|
2986
3381
|
// src/actions/promptUser.ts
|
|
@@ -3073,15 +3468,15 @@ async function confirmEntities(ctx) {
|
|
|
3073
3468
|
onSubmit: () => {
|
|
3074
3469
|
}
|
|
3075
3470
|
});
|
|
3076
|
-
const
|
|
3077
|
-
if (
|
|
3471
|
+
const confirmed3 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
|
|
3472
|
+
if (confirmed3.length === 0) {
|
|
3078
3473
|
throw new Error("User cancelled entity selection \u2014 analysis halted.");
|
|
3079
3474
|
}
|
|
3080
|
-
ctx.setUserInput("confirmedEntities",
|
|
3475
|
+
ctx.setUserInput("confirmedEntities", confirmed3);
|
|
3081
3476
|
track("AI Wizard Entities Confirmed", {
|
|
3082
|
-
entities: toEntitySummary(
|
|
3477
|
+
entities: toEntitySummary(confirmed3)
|
|
3083
3478
|
});
|
|
3084
|
-
return { ingestionAnalysis: entities, confirmedEntities:
|
|
3479
|
+
return { ingestionAnalysis: entities, confirmedEntities: confirmed3 };
|
|
3085
3480
|
}
|
|
3086
3481
|
|
|
3087
3482
|
// src/actions/review.ts
|
|
@@ -3105,7 +3500,7 @@ ${JSON.stringify(s.output, null, 2)}`
|
|
|
3105
3500
|
}
|
|
3106
3501
|
function formatReviewSummary(result) {
|
|
3107
3502
|
const nextStepLines = result.nextSteps.map((step) => {
|
|
3108
|
-
const isIngestCommand = step.includes("
|
|
3503
|
+
const isIngestCommand = step.includes("algolia-wizard/");
|
|
3109
3504
|
const isWorktreeCommand = step.includes("/worktrees/");
|
|
3110
3505
|
return {
|
|
3111
3506
|
text: `\u2192 ${step}`,
|
|
@@ -3147,13 +3542,14 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3147
3542
|
import z24 from "zod";
|
|
3148
3543
|
|
|
3149
3544
|
// src/lib/worktree.ts
|
|
3150
|
-
import { execFile
|
|
3151
|
-
import {
|
|
3545
|
+
import { execFile } from "node:child_process";
|
|
3546
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
3547
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir4, readFile as readFile9, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3152
3548
|
import {
|
|
3153
3549
|
basename as basename2,
|
|
3154
3550
|
dirname as dirname7,
|
|
3155
3551
|
isAbsolute as isAbsolute2,
|
|
3156
|
-
join as
|
|
3552
|
+
join as join12,
|
|
3157
3553
|
relative as relative2,
|
|
3158
3554
|
resolve as resolve3
|
|
3159
3555
|
} from "node:path";
|
|
@@ -3187,8 +3583,8 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
3187
3583
|
return out.trim().length > 0;
|
|
3188
3584
|
}
|
|
3189
3585
|
async function pruneOldWorktrees(repoRoot) {
|
|
3190
|
-
const dir =
|
|
3191
|
-
const stale = (await
|
|
3586
|
+
const dir = join12(stateDir(repoRoot), "worktrees");
|
|
3587
|
+
const stale = (await readdir4(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
3192
3588
|
for (const slug of stale) {
|
|
3193
3589
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
3194
3590
|
try {
|
|
@@ -3198,7 +3594,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3198
3594
|
"worktree",
|
|
3199
3595
|
"remove",
|
|
3200
3596
|
"--force",
|
|
3201
|
-
|
|
3597
|
+
join12(dir, slug)
|
|
3202
3598
|
]);
|
|
3203
3599
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
3204
3600
|
} catch (err) {
|
|
@@ -3212,43 +3608,55 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3212
3608
|
async function createWorktree(repoRoot) {
|
|
3213
3609
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
3214
3610
|
const dirSlug = branch.replace(/\//g, "-");
|
|
3215
|
-
const path =
|
|
3611
|
+
const path = join12(stateDir(repoRoot), "worktrees", dirSlug);
|
|
3216
3612
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
3217
3613
|
await pruneOldWorktrees(repoRoot);
|
|
3218
3614
|
await mkdir6(dirname7(path), { recursive: true });
|
|
3219
3615
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
3220
3616
|
return { path, branch };
|
|
3221
3617
|
}
|
|
3222
|
-
async function
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
return { ok: true, output: "no package.json; skipped install" };
|
|
3227
|
-
}
|
|
3228
|
-
const pm = await detectPackageManager(worktreePath);
|
|
3229
|
-
return new Promise((resolve4) => {
|
|
3230
|
-
let output = "";
|
|
3231
|
-
const child = spawn3(pm, ["install"], {
|
|
3232
|
-
cwd: worktreePath,
|
|
3233
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3234
|
-
});
|
|
3235
|
-
child.stdout?.on("data", (d) => output += d);
|
|
3236
|
-
child.stderr?.on("data", (d) => output += d);
|
|
3237
|
-
child.on(
|
|
3238
|
-
"error",
|
|
3239
|
-
(err) => resolve4({
|
|
3240
|
-
ok: false,
|
|
3241
|
-
output: `Failed to run ${pm} install: ${err.message}`
|
|
3242
|
-
})
|
|
3243
|
-
);
|
|
3244
|
-
child.on(
|
|
3245
|
-
"close",
|
|
3246
|
-
(code) => resolve4({ ok: code === 0, output: output.trim() })
|
|
3247
|
-
);
|
|
3618
|
+
async function spawnStep(worktreePath, argv) {
|
|
3619
|
+
const { code, output } = await runCommand(argv[0], [...argv.slice(1)], {
|
|
3620
|
+
cwd: worktreePath,
|
|
3621
|
+
timeoutMs: INSTALL_TIMEOUT_MS
|
|
3248
3622
|
});
|
|
3623
|
+
return { ok: code === 0, output: output.trim() };
|
|
3624
|
+
}
|
|
3625
|
+
async function installWorktreeDeps(worktreePath, toolchain) {
|
|
3626
|
+
const { profile, installSteps, packageManager } = toolchain;
|
|
3627
|
+
const declared = packageManager.dependency.mode === "agent-declares" ? packageManager.dependency.file : void 0;
|
|
3628
|
+
const haveSomethingToInstall = await hasProfileManifest(worktreePath, profile) || declared !== void 0 && existsSync4(join12(worktreePath, declared));
|
|
3629
|
+
if (!haveSomethingToInstall) {
|
|
3630
|
+
return {
|
|
3631
|
+
ok: true,
|
|
3632
|
+
output: `no ${profile.displayName} manifest; skipped install`
|
|
3633
|
+
};
|
|
3634
|
+
}
|
|
3635
|
+
if (installSteps.length === 0) {
|
|
3636
|
+
return {
|
|
3637
|
+
ok: true,
|
|
3638
|
+
output: `${profile.displayName} (${toolchain.packageManager.id}) has no wizard-run install step`
|
|
3639
|
+
};
|
|
3640
|
+
}
|
|
3641
|
+
const outputs = [];
|
|
3642
|
+
for (const step of installSteps) {
|
|
3643
|
+
if (step.requiresFile && !existsSync4(join12(worktreePath, step.requiresFile)))
|
|
3644
|
+
continue;
|
|
3645
|
+
const result = await spawnStep(worktreePath, step.argv);
|
|
3646
|
+
if (result.output) outputs.push(result.output);
|
|
3647
|
+
if (result.ok) continue;
|
|
3648
|
+
if (step.optional) {
|
|
3649
|
+
logger.warn(
|
|
3650
|
+
{ step: step.argv.join(" "), output: result.output },
|
|
3651
|
+
"installWorktreeDeps: optional install step failed; continuing"
|
|
3652
|
+
);
|
|
3653
|
+
continue;
|
|
3654
|
+
}
|
|
3655
|
+
return { ok: false, output: outputs.join("\n").trim() };
|
|
3656
|
+
}
|
|
3657
|
+
return { ok: true, output: outputs.join("\n").trim() };
|
|
3249
3658
|
}
|
|
3250
|
-
|
|
3251
|
-
function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
3659
|
+
function validateIngestEntrypoint(worktreePath, entrypoint, allowedExtensions) {
|
|
3252
3660
|
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
3253
3661
|
return {
|
|
3254
3662
|
ok: false,
|
|
@@ -3263,18 +3671,29 @@ function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
|
3263
3671
|
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
3264
3672
|
};
|
|
3265
3673
|
}
|
|
3674
|
+
if (allowedExtensions?.length && !allowedExtensions.some((ext) => entrypoint.endsWith(ext))) {
|
|
3675
|
+
return {
|
|
3676
|
+
ok: false,
|
|
3677
|
+
reason: `entrypoint "${entrypoint}" is not one of ${allowedExtensions.join(", ")}`
|
|
3678
|
+
};
|
|
3679
|
+
}
|
|
3266
3680
|
return { ok: true, target };
|
|
3267
3681
|
}
|
|
3268
|
-
async function runIngestScript(worktreePath,
|
|
3269
|
-
|
|
3682
|
+
async function runIngestScript(worktreePath, toolchain, entrypoint, env = {}) {
|
|
3683
|
+
const { ingest, profile, packageManager } = toolchain;
|
|
3684
|
+
if (ingest.kind !== "auto") {
|
|
3270
3685
|
return {
|
|
3271
3686
|
ran: false,
|
|
3272
3687
|
ok: false,
|
|
3273
3688
|
output: "",
|
|
3274
|
-
reason:
|
|
3689
|
+
reason: `${profile.displayName} (${packageManager.id}) projects must be run manually: ${ingest.runCommand}`
|
|
3275
3690
|
};
|
|
3276
3691
|
}
|
|
3277
|
-
const validated = validateIngestEntrypoint(
|
|
3692
|
+
const validated = validateIngestEntrypoint(
|
|
3693
|
+
worktreePath,
|
|
3694
|
+
entrypoint,
|
|
3695
|
+
ingest.entrypointExtensions
|
|
3696
|
+
);
|
|
3278
3697
|
if (!validated.ok) {
|
|
3279
3698
|
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
3280
3699
|
}
|
|
@@ -3295,29 +3714,13 @@ async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
|
3295
3714
|
reason: `entrypoint "${entrypoint}" does not exist`
|
|
3296
3715
|
};
|
|
3297
3716
|
}
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
3304
|
-
env: { ...process.env, ...env }
|
|
3305
|
-
});
|
|
3306
|
-
child.stdout?.on("data", (d) => output += d);
|
|
3307
|
-
child.stderr?.on("data", (d) => output += d);
|
|
3308
|
-
child.on(
|
|
3309
|
-
"error",
|
|
3310
|
-
(err) => resolveRun({
|
|
3311
|
-
ran: true,
|
|
3312
|
-
ok: false,
|
|
3313
|
-
output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
|
|
3314
|
-
})
|
|
3315
|
-
);
|
|
3316
|
-
child.on(
|
|
3317
|
-
"close",
|
|
3318
|
-
(code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
|
|
3319
|
-
);
|
|
3717
|
+
const argv = resolveIngestArgv(ingest, entrypoint);
|
|
3718
|
+
const { code, output } = await runCommand(argv[0], argv.slice(1), {
|
|
3719
|
+
cwd: worktreePath,
|
|
3720
|
+
env,
|
|
3721
|
+
timeoutMs: INGEST_TIMEOUT_MS
|
|
3320
3722
|
});
|
|
3723
|
+
return { ran: true, ok: code === 0, output: output.trim() };
|
|
3321
3724
|
}
|
|
3322
3725
|
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
3323
3726
|
const trimmed = sourcePath.trim();
|
|
@@ -3332,8 +3735,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3332
3735
|
} catch {
|
|
3333
3736
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
3334
3737
|
}
|
|
3335
|
-
const relPath =
|
|
3336
|
-
const dest =
|
|
3738
|
+
const relPath = join12(ingestDir, basename2(source));
|
|
3739
|
+
const dest = join12(worktreePath, relPath);
|
|
3337
3740
|
try {
|
|
3338
3741
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
3339
3742
|
await copyFile(source, dest);
|
|
@@ -3349,10 +3752,10 @@ function hasEnvVar(content, name) {
|
|
|
3349
3752
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
3350
3753
|
}
|
|
3351
3754
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
3352
|
-
const target =
|
|
3755
|
+
const target = join12(worktreePath, ".env");
|
|
3353
3756
|
let existing = "";
|
|
3354
3757
|
try {
|
|
3355
|
-
existing = await
|
|
3758
|
+
existing = await readFile9(target, "utf8");
|
|
3356
3759
|
} catch (err) {
|
|
3357
3760
|
if (err.code !== "ENOENT") throw err;
|
|
3358
3761
|
}
|
|
@@ -3469,69 +3872,33 @@ async function resolveSearchOnlyKey(index) {
|
|
|
3469
3872
|
}
|
|
3470
3873
|
|
|
3471
3874
|
// src/lib/algoliaDocs.ts
|
|
3472
|
-
import { readFileSync,
|
|
3473
|
-
import { dirname as dirname8, join as
|
|
3875
|
+
import { readFileSync, existsSync as existsSync5 } from "node:fs";
|
|
3876
|
+
import { dirname as dirname8, join as join13 } from "node:path";
|
|
3474
3877
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3475
|
-
var DOCS_SUBPATH =
|
|
3878
|
+
var DOCS_SUBPATH = join13("docs", "algolia-sdk");
|
|
3476
3879
|
function findDocsDir() {
|
|
3477
3880
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
3478
3881
|
for (; ; ) {
|
|
3479
|
-
const candidate =
|
|
3480
|
-
if (
|
|
3882
|
+
const candidate = join13(dir, DOCS_SUBPATH);
|
|
3883
|
+
if (existsSync5(candidate)) return candidate;
|
|
3481
3884
|
const parent = dirname8(dir);
|
|
3482
3885
|
if (parent === dir) return void 0;
|
|
3483
3886
|
dir = parent;
|
|
3484
3887
|
}
|
|
3485
3888
|
}
|
|
3486
|
-
function
|
|
3487
|
-
const docsDir = findDocsDir();
|
|
3488
|
-
if (!docsDir) {
|
|
3489
|
-
logger.warn(
|
|
3490
|
-
"algoliaDocs: docs/algolia-sdk not found; skipping SDK reference"
|
|
3491
|
-
);
|
|
3492
|
-
return "";
|
|
3493
|
-
}
|
|
3494
|
-
const files = readdirSync(docsDir).filter((f) => f.includes(language));
|
|
3495
|
-
if (files.length === 0) {
|
|
3496
|
-
logger.warn(
|
|
3497
|
-
{ language },
|
|
3498
|
-
"algoliaDocs: no SDK reference found for language; skipping"
|
|
3499
|
-
);
|
|
3500
|
-
return "";
|
|
3501
|
-
}
|
|
3502
|
-
return readFileSync(join11(docsDir, files[0]), "utf8").trim();
|
|
3503
|
-
}
|
|
3504
|
-
function getNamedDoc(name, language) {
|
|
3889
|
+
function getNamedDoc(name, key) {
|
|
3505
3890
|
const docsDir = findDocsDir();
|
|
3506
3891
|
if (!docsDir) {
|
|
3507
3892
|
logger.warn("docs/algolia-sdk not found");
|
|
3508
3893
|
return "";
|
|
3509
3894
|
}
|
|
3510
|
-
const file =
|
|
3511
|
-
if (!
|
|
3512
|
-
logger.warn({ name,
|
|
3895
|
+
const file = join13(docsDir, `${name}-${key}.md`);
|
|
3896
|
+
if (!existsSync5(file)) {
|
|
3897
|
+
logger.warn({ name, key }, "named SDK reference not found");
|
|
3513
3898
|
return "";
|
|
3514
3899
|
}
|
|
3515
3900
|
return readFileSync(file, "utf8").trim();
|
|
3516
3901
|
}
|
|
3517
|
-
function getFrameworkSpecificDoc(frameworks) {
|
|
3518
|
-
const fw = frameworks.map((f) => f.toLowerCase());
|
|
3519
|
-
if (fw.includes("vue") || fw.includes("nuxt")) {
|
|
3520
|
-
return loadAlgoliaDoc("vue");
|
|
3521
|
-
}
|
|
3522
|
-
if (fw.includes("react") || fw.includes("next.js")) {
|
|
3523
|
-
return loadAlgoliaDoc("react");
|
|
3524
|
-
}
|
|
3525
|
-
if (fw.includes("angular")) {
|
|
3526
|
-
return loadAlgoliaDoc("angular");
|
|
3527
|
-
}
|
|
3528
|
-
return loadAlgoliaDoc("js");
|
|
3529
|
-
}
|
|
3530
|
-
|
|
3531
|
-
// src/lib/shell.ts
|
|
3532
|
-
function shellQuote(value) {
|
|
3533
|
-
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
3534
|
-
}
|
|
3535
3902
|
|
|
3536
3903
|
// src/actions/implement.ts
|
|
3537
3904
|
var implementSchema = z24.object({
|
|
@@ -3566,12 +3933,11 @@ var implementSchema = z24.object({
|
|
|
3566
3933
|
});
|
|
3567
3934
|
var implementationOutputSchema = z24.object({
|
|
3568
3935
|
summary: z24.string(),
|
|
3569
|
-
// Ingestion only:
|
|
3570
|
-
//
|
|
3571
|
-
//
|
|
3572
|
-
//
|
|
3573
|
-
// the agent
|
|
3574
|
-
runtime: z24.enum(INGEST_RUNTIMES).optional(),
|
|
3936
|
+
// Ingestion only: the script the wizard should run, as a bare path — never a
|
|
3937
|
+
// command string, and never the interpreter. The command comes from the
|
|
3938
|
+
// resolved language toolchain (a registry constant); this path is validated to
|
|
3939
|
+
// a worktree-relative file with a runnable extension and substituted into it.
|
|
3940
|
+
// So the agent contributes no part of the command that gets executed.
|
|
3575
3941
|
entrypoint: z24.string().optional()
|
|
3576
3942
|
});
|
|
3577
3943
|
var verificationOutputSchema = z24.object({
|
|
@@ -3581,47 +3947,11 @@ var verificationOutputSchema = z24.object({
|
|
|
3581
3947
|
});
|
|
3582
3948
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
3583
3949
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
if (names.some((n) => n.includes("react") || n.includes("next")))
|
|
3589
|
-
return "React";
|
|
3590
|
-
if (names.some((n) => n.includes("angular"))) return "Angular";
|
|
3591
|
-
return "JavaScript";
|
|
3592
|
-
}
|
|
3593
|
-
function frameworksForDoc(framework) {
|
|
3594
|
-
switch (framework) {
|
|
3595
|
-
case "React":
|
|
3596
|
-
return ["react"];
|
|
3597
|
-
case "Vue":
|
|
3598
|
-
return ["vue"];
|
|
3599
|
-
case "Angular":
|
|
3600
|
-
return ["angular"];
|
|
3601
|
-
case "JavaScript":
|
|
3602
|
-
return [];
|
|
3603
|
-
}
|
|
3604
|
-
}
|
|
3605
|
-
function publicEnvPrefix(language) {
|
|
3606
|
-
const frameworkNames = language.frameworks.map(
|
|
3607
|
-
(framework) => framework.name.toLowerCase()
|
|
3950
|
+
function buildSearchEnvVars(language, strategy, appId, searchKey) {
|
|
3951
|
+
const prefix = publicEnvPrefix(
|
|
3952
|
+
language.frameworks.map((framework) => framework.name),
|
|
3953
|
+
strategy
|
|
3608
3954
|
);
|
|
3609
|
-
if (frameworkNames.some((name) => name.includes("next"))) {
|
|
3610
|
-
return "NEXT_PUBLIC_";
|
|
3611
|
-
}
|
|
3612
|
-
if (frameworkNames.some((name) => name.includes("nuxt"))) {
|
|
3613
|
-
return "NUXT_PUBLIC_";
|
|
3614
|
-
}
|
|
3615
|
-
if (frameworkNames.some((name) => name.includes("astro"))) {
|
|
3616
|
-
return "PUBLIC_";
|
|
3617
|
-
}
|
|
3618
|
-
if (frameworkNames.some((name) => name.includes("vite"))) {
|
|
3619
|
-
return "VITE_";
|
|
3620
|
-
}
|
|
3621
|
-
return "PUBLIC_";
|
|
3622
|
-
}
|
|
3623
|
-
function searchEnvVars(language, appId, searchKey) {
|
|
3624
|
-
const prefix = publicEnvPrefix(language);
|
|
3625
3955
|
return [
|
|
3626
3956
|
{
|
|
3627
3957
|
name: `${prefix}ALGOLIA_APP_ID`,
|
|
@@ -3633,6 +3963,38 @@ function searchEnvVars(language, appId, searchKey) {
|
|
|
3633
3963
|
}
|
|
3634
3964
|
];
|
|
3635
3965
|
}
|
|
3966
|
+
async function resolveIngestionProfile(ctx, language, repoRoot) {
|
|
3967
|
+
const { candidates, confirmed: confirmed3, onDisk } = await pickIngestionCandidates(
|
|
3968
|
+
repoRoot,
|
|
3969
|
+
language.languages.map((l) => l.name)
|
|
3970
|
+
);
|
|
3971
|
+
if (candidates.length === 0) {
|
|
3972
|
+
const chosen = confirmed3[0] ?? onDisk[0] ?? LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID];
|
|
3973
|
+
logger.warn(
|
|
3974
|
+
{
|
|
3975
|
+
confirmed: language.languages.map((l) => l.name),
|
|
3976
|
+
onDisk: onDisk.map((p) => p.id),
|
|
3977
|
+
chosen: chosen.id
|
|
3978
|
+
},
|
|
3979
|
+
"implement: no confirmed language matched a manifest on disk; falling back"
|
|
3980
|
+
);
|
|
3981
|
+
return chosen;
|
|
3982
|
+
}
|
|
3983
|
+
if (candidates.length === 1) return candidates[0];
|
|
3984
|
+
const backends = candidates.filter(isBackendLanguage);
|
|
3985
|
+
if (backends.length === 1) return backends[0];
|
|
3986
|
+
if (backends.length === 0) return candidates[0];
|
|
3987
|
+
if (isBackendLanguage(candidates[0])) return candidates[0];
|
|
3988
|
+
const options = backends.map((p) => p.displayName);
|
|
3989
|
+
const selection = await ctx.requestUserInput({
|
|
3990
|
+
prompt: "Which language should the ingestion script use?",
|
|
3991
|
+
promptType: "multipleChoice",
|
|
3992
|
+
options,
|
|
3993
|
+
defaultSelectedIndex: 0
|
|
3994
|
+
});
|
|
3995
|
+
const picked = typeof selection === "string" ? backends.find((p) => p.displayName === selection) : void 0;
|
|
3996
|
+
return picked ?? backends[0];
|
|
3997
|
+
}
|
|
3636
3998
|
function baseInstructions(input) {
|
|
3637
3999
|
return [
|
|
3638
4000
|
`Target Algolia index: ${input.targetIndex}`,
|
|
@@ -3660,37 +4022,48 @@ function sourceSpecificInstructions(input) {
|
|
|
3660
4022
|
generated: [
|
|
3661
4023
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
3662
4024
|
"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.",
|
|
3663
|
-
"In the script, read and parse each returned file path at runtime
|
|
4025
|
+
"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.",
|
|
3664
4026
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
3665
4027
|
]
|
|
3666
4028
|
};
|
|
3667
4029
|
return byLine[input.ingestionSource];
|
|
3668
4030
|
}
|
|
3669
4031
|
function ingestionInstructions(input) {
|
|
4032
|
+
const { ingestionProfile: profile, toolchain } = input;
|
|
4033
|
+
const { ingest } = toolchain;
|
|
4034
|
+
const extensions = ingest.entrypointExtensions.join(", ");
|
|
4035
|
+
const runInstruction = ingest.kind === "auto" ? `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard runs it with \`${describeIngestCommand(ingest, profile.ingestEntrypointExample)}\`, so it must be a plain path with no flags or arguments and must run as-is under that command.` : `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard does NOT run ${profile.displayName} ${toolchain.packageManager.id} projects itself \u2014 it tells the developer to run \`${describeIngestCommand(ingest, profile.ingestEntrypointExample)}\`, so also add whatever build configuration that command needs${ingest.kind === "manual" && ingest.requiresBuildTask ? `, including a "${ingest.requiresBuildTask}" task in "${toolchain.packageManager.dependency.mode === "agent-declares" ? toolchain.packageManager.dependency.file : "the build file"}" that runs the script` : ""}.`;
|
|
3670
4036
|
return [
|
|
3671
4037
|
...input.confirmed && input.confirmed.length ? [
|
|
3672
|
-
`
|
|
4038
|
+
`Write the ingestion script in ${profile.displayName}, at "${ingestScriptDir(profile)}/" in the repo.`,
|
|
3673
4039
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
3674
|
-
`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.`,
|
|
3675
|
-
|
|
4040
|
+
`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. ${profile.envReadInstruction} The wizard sets these when it runs the script.`,
|
|
4041
|
+
`Use the official Algolia ${profile.displayName} client (${profile.sdk.packageName}). Do not use the raw HTTP API, and do not use a client for another language.`,
|
|
3676
4042
|
"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.",
|
|
3677
|
-
getNamedDoc("save-records",
|
|
3678
|
-
|
|
4043
|
+
getNamedDoc("save-records", profile.sdk.docKey),
|
|
4044
|
+
dependencyInstruction(toolchain),
|
|
3679
4045
|
"The summary should be extremely concise.",
|
|
3680
|
-
|
|
4046
|
+
runInstruction,
|
|
3681
4047
|
...sourceSpecificInstructions(input)
|
|
3682
4048
|
] : []
|
|
3683
4049
|
];
|
|
3684
4050
|
}
|
|
3685
4051
|
function searchInstructions(input) {
|
|
3686
|
-
const doc =
|
|
4052
|
+
const doc = getNamedDoc(
|
|
4053
|
+
"instantsearch-setup",
|
|
4054
|
+
searchDocKey(input.searchStrategy)
|
|
4055
|
+
);
|
|
4056
|
+
const isTemplate = input.searchStrategy === "cdn-template";
|
|
4057
|
+
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.`;
|
|
3687
4058
|
return [
|
|
3688
4059
|
"Implement an in-app Algolia search experience.",
|
|
3689
|
-
`Build the search UI for ${input.
|
|
3690
|
-
"Follow the Algolia
|
|
4060
|
+
`Build the search UI for ${describeSearchTarget(input.searchStrategy, input.frameworkName)}.`,
|
|
4061
|
+
"Follow the Algolia reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3691
4062
|
doc,
|
|
3692
|
-
|
|
3693
|
-
|
|
4063
|
+
placement,
|
|
4064
|
+
`It needs at least a working SearchBox and Hits against the "${input.targetIndex}" index.`,
|
|
4065
|
+
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.',
|
|
4066
|
+
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.",
|
|
3694
4067
|
// appId always resolves (loadActiveProfile throws otherwise); only the
|
|
3695
4068
|
// search-only key is best-effort and can fall back to a placeholder.
|
|
3696
4069
|
`Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
@@ -3698,20 +4071,22 @@ function searchInstructions(input) {
|
|
|
3698
4071
|
// resolved app id / search-only key into ".env" under these exact names
|
|
3699
4072
|
// right after this step, so a renamed prefix here would leave the code
|
|
3700
4073
|
// reading a var the wizard never wrote.
|
|
3701
|
-
`Use exactly these
|
|
3702
|
-
'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.',
|
|
4074
|
+
`Use exactly these env var names: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3703
4075
|
"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."
|
|
3704
4076
|
];
|
|
3705
4077
|
}
|
|
3706
4078
|
function verificationInstructions(input) {
|
|
4079
|
+
const protectedDirs = [
|
|
4080
|
+
.../* @__PURE__ */ new Set([input.ingestDir, ingestScriptDir(input.ingestionProfile)])
|
|
4081
|
+
];
|
|
3707
4082
|
return [
|
|
3708
4083
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
3709
4084
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
3710
|
-
|
|
4085
|
+
`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.`,
|
|
3711
4086
|
"For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
|
|
3712
4087
|
"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.",
|
|
3713
4088
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
3714
|
-
`Do not modify "${
|
|
4089
|
+
`Do not modify ${protectedDirs.map((dir) => `"${dir}/"`).join(" or ")} unless verifyImplementation reports an actionable issue in its files.`,
|
|
3715
4090
|
"Always call reportStatus with status=success once verification has run, even when sufficient=false.",
|
|
3716
4091
|
"Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
|
|
3717
4092
|
"Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
|
|
@@ -3720,14 +4095,17 @@ function verificationInstructions(input) {
|
|
|
3720
4095
|
var IMPLEMENT_CONFIG = {
|
|
3721
4096
|
ingestion: {
|
|
3722
4097
|
title: "Algolia ingestion",
|
|
4098
|
+
label: "Ingestion",
|
|
3723
4099
|
buildInstructions: ingestionInstructions
|
|
3724
4100
|
},
|
|
3725
4101
|
search: {
|
|
3726
4102
|
title: "Algolia search",
|
|
4103
|
+
label: "Search",
|
|
3727
4104
|
buildInstructions: searchInstructions
|
|
3728
4105
|
},
|
|
3729
4106
|
verification: {
|
|
3730
4107
|
title: "Algolia verification",
|
|
4108
|
+
label: "Verification",
|
|
3731
4109
|
buildInstructions: verificationInstructions
|
|
3732
4110
|
}
|
|
3733
4111
|
};
|
|
@@ -3759,11 +4137,10 @@ function buildAgentInstructions(useCase, input, extraInstructions = []) {
|
|
|
3759
4137
|
];
|
|
3760
4138
|
}
|
|
3761
4139
|
function formatSummary(useCase, summary) {
|
|
3762
|
-
|
|
3763
|
-
return `${label}: ${summary}`;
|
|
4140
|
+
return `${IMPLEMENT_CONFIG[useCase].label}: ${summary}`;
|
|
3764
4141
|
}
|
|
3765
|
-
function buildIngestCommand(worktree,
|
|
3766
|
-
return `cd ${shellQuote(worktree)} && ${
|
|
4142
|
+
function buildIngestCommand(worktree, toolchain, entrypoint) {
|
|
4143
|
+
return `cd ${shellQuote(worktree)} && ${describeIngestCommand(toolchain.ingest, entrypoint)}`;
|
|
3767
4144
|
}
|
|
3768
4145
|
function parseIngestRecordCount(output) {
|
|
3769
4146
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
@@ -3845,7 +4222,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3845
4222
|
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
3846
4223
|
}
|
|
3847
4224
|
const normalized = normalizeFindingPaths(findings);
|
|
3848
|
-
const
|
|
4225
|
+
const confirmed3 = normalized.confirmedEntities;
|
|
3849
4226
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
3850
4227
|
let appId;
|
|
3851
4228
|
let searchKey;
|
|
@@ -3883,31 +4260,66 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3883
4260
|
);
|
|
3884
4261
|
}
|
|
3885
4262
|
}
|
|
4263
|
+
const ingestionProfile = await resolveIngestionProfile(
|
|
4264
|
+
ctx,
|
|
4265
|
+
language,
|
|
4266
|
+
worktree
|
|
4267
|
+
);
|
|
4268
|
+
const toolchain = await resolveToolchain(worktree, ingestionProfile);
|
|
4269
|
+
const verificationLanguages = [
|
|
4270
|
+
.../* @__PURE__ */ new Set([
|
|
4271
|
+
ingestionProfile.id,
|
|
4272
|
+
...(await detectProfilesFromManifests(worktree)).map((p) => p.id)
|
|
4273
|
+
])
|
|
4274
|
+
];
|
|
4275
|
+
const frameworkName = language.frameworks[0]?.name;
|
|
4276
|
+
const searchStrategy = resolveSearchStrategy(
|
|
4277
|
+
frameworkName,
|
|
4278
|
+
verificationLanguages.includes(JAVASCRIPT)
|
|
4279
|
+
);
|
|
4280
|
+
logger.info(
|
|
4281
|
+
{
|
|
4282
|
+
language: ingestionProfile.id,
|
|
4283
|
+
packageManager: toolchain.packageManager.id,
|
|
4284
|
+
ingest: toolchain.ingest.kind,
|
|
4285
|
+
framework: frameworkName,
|
|
4286
|
+
searchStrategy
|
|
4287
|
+
},
|
|
4288
|
+
"implement: resolved ingestion toolchain and search strategy"
|
|
4289
|
+
);
|
|
3886
4290
|
const input = {
|
|
3887
4291
|
findings: normalized,
|
|
3888
|
-
confirmed:
|
|
4292
|
+
confirmed: confirmed3,
|
|
3889
4293
|
searchLocation,
|
|
3890
4294
|
targetIndex,
|
|
3891
4295
|
language,
|
|
3892
4296
|
appId,
|
|
3893
4297
|
searchKey,
|
|
3894
|
-
searchEnvVars:
|
|
4298
|
+
searchEnvVars: buildSearchEnvVars(
|
|
4299
|
+
language,
|
|
4300
|
+
searchStrategy,
|
|
4301
|
+
appId,
|
|
4302
|
+
searchKey
|
|
4303
|
+
),
|
|
3895
4304
|
ingestDir: INGEST_DIR,
|
|
3896
4305
|
ingestionSource,
|
|
3897
4306
|
uploadFilePath,
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
4307
|
+
searchStrategy,
|
|
4308
|
+
frameworkName,
|
|
4309
|
+
ingestionProfile,
|
|
4310
|
+
toolchain,
|
|
4311
|
+
verificationLanguages
|
|
3901
4312
|
};
|
|
4313
|
+
const searchToolchain = !bundlesJavaScript(searchStrategy) ? void 0 : ingestionProfile.id === JAVASCRIPT ? toolchain : await resolveToolchain(worktree, LANGUAGE_PROFILES[JAVASCRIPT]);
|
|
4314
|
+
const toolchainForUseCase = (useCase) => useCase === "search" ? searchToolchain : toolchain;
|
|
3902
4315
|
const summaries = [];
|
|
3903
4316
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
3904
4317
|
let agentRuns = 0;
|
|
3905
|
-
let ingestRuntime;
|
|
3906
4318
|
let ingestEntrypoint;
|
|
3907
4319
|
let ingestScriptRan = false;
|
|
3908
4320
|
let ingestRecordCount;
|
|
3909
4321
|
let ingestDurationMs;
|
|
3910
|
-
|
|
4322
|
+
const failedInstalls = /* @__PURE__ */ new Set();
|
|
3911
4323
|
let ingestOutcomeMessage;
|
|
3912
4324
|
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
3913
4325
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
@@ -3921,16 +4333,19 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3921
4333
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
3922
4334
|
outputSchema: implementationOutputSchema
|
|
3923
4335
|
});
|
|
4336
|
+
const useCaseToolchain = toolchainForUseCase(currentUseCase);
|
|
4337
|
+
if (!useCaseToolchain) return result;
|
|
3924
4338
|
ctx.notify({
|
|
3925
4339
|
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
3926
4340
|
});
|
|
3927
4341
|
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
3928
|
-
useCase: currentUseCase
|
|
4342
|
+
useCase: currentUseCase,
|
|
4343
|
+
language: useCaseToolchain.profile.id
|
|
3929
4344
|
});
|
|
3930
|
-
const install = await installWorktreeDeps(worktree);
|
|
4345
|
+
const install = await installWorktreeDeps(worktree, useCaseToolchain);
|
|
3931
4346
|
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
3932
4347
|
if (!install.ok) {
|
|
3933
|
-
|
|
4348
|
+
failedInstalls.add(useCaseToolchain.profile.displayName);
|
|
3934
4349
|
logger.warn(
|
|
3935
4350
|
{ useCase: currentUseCase, output: install.output },
|
|
3936
4351
|
"implement: dependency install in worktree failed; generated commands may not run until deps are installed"
|
|
@@ -3944,15 +4359,16 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3944
4359
|
return runAgent({
|
|
3945
4360
|
instructions: buildAgentInstructions("verification", input),
|
|
3946
4361
|
tools: toolsForUseCase("verification"),
|
|
3947
|
-
outputSchema: verificationOutputSchema
|
|
4362
|
+
outputSchema: verificationOutputSchema,
|
|
4363
|
+
// So verifyImplementation runs this repo's checks, not just npm scripts.
|
|
4364
|
+
languages: input.verificationLanguages
|
|
3948
4365
|
});
|
|
3949
4366
|
}
|
|
3950
4367
|
if (useCases.includes("ingestion")) {
|
|
3951
|
-
const { summary,
|
|
4368
|
+
const { summary, entrypoint } = await runImplementationUseCase("ingestion");
|
|
3952
4369
|
summaries.push(formatSummary("ingestion", summary));
|
|
3953
|
-
ingestRuntime = runtime;
|
|
3954
4370
|
ingestEntrypoint = entrypoint;
|
|
3955
|
-
if (
|
|
4371
|
+
if (ingestEntrypoint && toolchain.ingest.kind === "auto" && failedInstalls.size === 0) {
|
|
3956
4372
|
ctx.clearNotices();
|
|
3957
4373
|
const runNow = await ctx.requestUserInput({
|
|
3958
4374
|
prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
|
|
@@ -3964,13 +4380,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3964
4380
|
const profile = await loadActiveProfile();
|
|
3965
4381
|
ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
|
|
3966
4382
|
const scriptLogId = ctx.logStart("runIngestScript", {
|
|
3967
|
-
|
|
4383
|
+
language: ingestionProfile.id,
|
|
3968
4384
|
entrypoint: ingestEntrypoint
|
|
3969
4385
|
});
|
|
3970
4386
|
const startedAt = Date.now();
|
|
3971
4387
|
const run2 = await runIngestScript(
|
|
3972
4388
|
worktree,
|
|
3973
|
-
|
|
4389
|
+
toolchain,
|
|
3974
4390
|
ingestEntrypoint,
|
|
3975
4391
|
{
|
|
3976
4392
|
[APP_ID_VAR]: profile.appId,
|
|
@@ -3984,7 +4400,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3984
4400
|
ingestRecordCount = parseIngestRecordCount(run2.output);
|
|
3985
4401
|
if (ingestRecordCount != null) {
|
|
3986
4402
|
track("AI Wizard Ingest Successful", {
|
|
3987
|
-
entity_name:
|
|
4403
|
+
entity_name: confirmed3?.map((e) => e.name).join(", ") || "unknown",
|
|
3988
4404
|
record_count: ingestRecordCount,
|
|
3989
4405
|
duration_ms: ingestDurationMs
|
|
3990
4406
|
});
|
|
@@ -3997,7 +4413,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3997
4413
|
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
|
|
3998
4414
|
logger.warn(
|
|
3999
4415
|
{
|
|
4000
|
-
|
|
4416
|
+
language: ingestionProfile.id,
|
|
4001
4417
|
entrypoint: ingestEntrypoint,
|
|
4002
4418
|
reason: run2.reason
|
|
4003
4419
|
},
|
|
@@ -4020,7 +4436,7 @@ ${run2.output}` : status;
|
|
|
4020
4436
|
outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
|
|
4021
4437
|
logger.warn(
|
|
4022
4438
|
{
|
|
4023
|
-
|
|
4439
|
+
language: ingestionProfile.id,
|
|
4024
4440
|
entrypoint: ingestEntrypoint,
|
|
4025
4441
|
output: run2.output
|
|
4026
4442
|
},
|
|
@@ -4037,10 +4453,28 @@ ${run2.output}` : status;
|
|
|
4037
4453
|
}
|
|
4038
4454
|
}
|
|
4039
4455
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
4040
|
-
if (
|
|
4456
|
+
if (ingestEntrypoint) {
|
|
4041
4457
|
commandMessages.push(
|
|
4042
|
-
`Ingestion command: ${buildIngestCommand(worktree,
|
|
4458
|
+
`Ingestion command: ${buildIngestCommand(worktree, toolchain, ingestEntrypoint)}`
|
|
4043
4459
|
);
|
|
4460
|
+
if (toolchain.ingest.kind === "manual") {
|
|
4461
|
+
commandMessages.push(
|
|
4462
|
+
`The wizard does not run ${ingestionProfile.displayName} ${toolchain.packageManager.id} projects \u2014 run the command above yourself to ingest.`
|
|
4463
|
+
);
|
|
4464
|
+
const missingTask = await missingBuildTask(worktree, toolchain);
|
|
4465
|
+
if (missingTask) {
|
|
4466
|
+
const warning = `\u26A0\uFE0F The command above needs a "${missingTask}" task, which is not in ${toolchain.packageManager.dependency.mode === "agent-declares" ? toolchain.packageManager.dependency.file : "the build file"} \u2014 add it before running, or run the script through your IDE instead.`;
|
|
4467
|
+
commandMessages.push(warning);
|
|
4468
|
+
summaries.push(warning);
|
|
4469
|
+
}
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
4472
|
+
if (ingestionSource === "local") {
|
|
4473
|
+
const limitation = localSourceLimitation(worktree, ingestionProfile);
|
|
4474
|
+
if (limitation) {
|
|
4475
|
+
commandMessages.push(`\u26A0\uFE0F ${limitation}`);
|
|
4476
|
+
summaries.push(`\u26A0\uFE0F ${limitation}`);
|
|
4477
|
+
}
|
|
4044
4478
|
}
|
|
4045
4479
|
await ctx.requestUserInput({
|
|
4046
4480
|
// No question being asked here, just an acknowledgement — the
|
|
@@ -4051,7 +4485,20 @@ ${run2.output}` : status;
|
|
|
4051
4485
|
messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
|
|
4052
4486
|
});
|
|
4053
4487
|
}
|
|
4054
|
-
|
|
4488
|
+
const skipSearch = useCases.includes("search") && !canScaffoldSearchUI(input.searchStrategy);
|
|
4489
|
+
if (skipSearch) {
|
|
4490
|
+
const target = describeSearchTarget(
|
|
4491
|
+
input.searchStrategy,
|
|
4492
|
+
input.frameworkName
|
|
4493
|
+
);
|
|
4494
|
+
summaries.push(
|
|
4495
|
+
`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).`
|
|
4496
|
+
);
|
|
4497
|
+
track("AI Wizard Search UI Skipped", {
|
|
4498
|
+
framework: input.frameworkName ?? "unknown"
|
|
4499
|
+
});
|
|
4500
|
+
}
|
|
4501
|
+
if (useCases.includes("search") && !skipSearch) {
|
|
4055
4502
|
let extraInstructions = [];
|
|
4056
4503
|
const preSearchFiles = new Set(await listChangedFiles(worktree));
|
|
4057
4504
|
for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
|
|
@@ -4122,9 +4569,9 @@ ${run2.output}` : status;
|
|
|
4122
4569
|
"implement: agent reported success but no files changed in the worktree"
|
|
4123
4570
|
);
|
|
4124
4571
|
}
|
|
4125
|
-
if (
|
|
4572
|
+
if (failedInstalls.size > 0) {
|
|
4126
4573
|
summaries.push(
|
|
4127
|
-
|
|
4574
|
+
`\u26A0\uFE0F Dependency install in the worktree failed. Install the ${[...failedInstalls].join(" and ")} dependencies in the worktree before the command below, or it will fail on a missing package.`
|
|
4128
4575
|
);
|
|
4129
4576
|
}
|
|
4130
4577
|
return {
|
|
@@ -4132,10 +4579,10 @@ ${run2.output}` : status;
|
|
|
4132
4579
|
filesChanged,
|
|
4133
4580
|
summary: summaries.join("\n\n"),
|
|
4134
4581
|
worktreePath: worktree,
|
|
4135
|
-
...useCases.includes("ingestion") &&
|
|
4582
|
+
...useCases.includes("ingestion") && ingestEntrypoint ? {
|
|
4136
4583
|
ingestCommand: buildIngestCommand(
|
|
4137
4584
|
worktree,
|
|
4138
|
-
|
|
4585
|
+
toolchain,
|
|
4139
4586
|
ingestEntrypoint
|
|
4140
4587
|
),
|
|
4141
4588
|
ingestScriptRan,
|
|
@@ -4464,20 +4911,20 @@ function parseCliArgs(argv) {
|
|
|
4464
4911
|
}
|
|
4465
4912
|
|
|
4466
4913
|
// src/lib/resetState.ts
|
|
4467
|
-
import { readdir as
|
|
4468
|
-
import { join as
|
|
4914
|
+
import { readdir as readdir5, rm as rm2 } from "node:fs/promises";
|
|
4915
|
+
import { join as join14 } from "node:path";
|
|
4469
4916
|
var KEEP = ["wizard.log"];
|
|
4470
4917
|
async function resetProjectState() {
|
|
4471
4918
|
const dir = stateDir();
|
|
4472
4919
|
let entries;
|
|
4473
4920
|
try {
|
|
4474
|
-
entries = await
|
|
4921
|
+
entries = await readdir5(dir);
|
|
4475
4922
|
} catch {
|
|
4476
4923
|
return { dir, removed: [] };
|
|
4477
4924
|
}
|
|
4478
4925
|
const targets = entries.filter((name) => !KEEP.includes(name));
|
|
4479
4926
|
await Promise.all(
|
|
4480
|
-
targets.map((name) => rm2(
|
|
4927
|
+
targets.map((name) => rm2(join14(dir, name), { recursive: true, force: true }))
|
|
4481
4928
|
);
|
|
4482
4929
|
return { dir, removed: targets };
|
|
4483
4930
|
}
|