@beignet/cli 0.0.40 → 0.0.41
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/CHANGELOG.md +9 -0
- package/README.md +76 -10
- package/dist/analysis/source-index.d.ts +38 -0
- package/dist/analysis/source-index.d.ts.map +1 -0
- package/dist/analysis/source-index.js +271 -0
- package/dist/analysis/source-index.js.map +1 -0
- package/dist/analysis/workspace.d.ts +16 -0
- package/dist/analysis/workspace.d.ts.map +1 -0
- package/dist/analysis/workspace.js +134 -0
- package/dist/analysis/workspace.js.map +1 -0
- package/dist/app-map-schema.d.ts +5 -0
- package/dist/app-map-schema.d.ts.map +1 -0
- package/dist/app-map-schema.js +26 -0
- package/dist/app-map-schema.js.map +1 -0
- package/dist/app-map.d.ts +96 -0
- package/dist/app-map.d.ts.map +1 -0
- package/dist/app-map.js +1141 -0
- package/dist/app-map.js.map +1 -0
- package/dist/check.d.ts +7 -0
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +20 -5
- package/dist/check.js.map +1 -1
- package/dist/db.d.ts +32 -7
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +182 -14
- package/dist/db.js.map +1 -1
- package/dist/explain.d.ts +98 -0
- package/dist/explain.d.ts.map +1 -0
- package/dist/explain.js +512 -0
- package/dist/explain.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +79 -2
- package/dist/index.js.map +1 -1
- package/dist/inspect.js +53 -3
- package/dist/inspect.js.map +1 -1
- package/dist/lib.d.ts +4 -0
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +2 -0
- package/dist/lib.js.map +1 -1
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +63 -2
- package/dist/make.js.map +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +69 -1
- package/dist/mcp.js.map +1 -1
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +10 -0
- package/dist/preflight.js.map +1 -1
- package/dist/provider-add.d.ts.map +1 -1
- package/dist/provider-add.js +167 -13
- package/dist/provider-add.js.map +1 -1
- package/dist/provider-audit.d.ts +6 -0
- package/dist/provider-audit.d.ts.map +1 -1
- package/dist/provider-audit.js +63 -15
- package/dist/provider-audit.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +17 -4
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +10 -4
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/index.d.ts +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +8 -1
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/server.d.ts +3 -0
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +55 -1
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shared.js +1 -1
- package/package.json +2 -2
- package/skills/app-structure/SKILL.md +22 -4
- package/src/analysis/source-index.ts +395 -0
- package/src/analysis/workspace.ts +180 -0
- package/src/app-map-schema.ts +28 -0
- package/src/app-map.ts +1705 -0
- package/src/check.ts +27 -4
- package/src/db.ts +232 -14
- package/src/explain.ts +786 -0
- package/src/index.ts +113 -2
- package/src/inspect.ts +70 -2
- package/src/lib.ts +36 -0
- package/src/make.ts +90 -2
- package/src/mcp.ts +107 -1
- package/src/preflight.ts +14 -0
- package/src/provider-add.ts +211 -12
- package/src/provider-audit.ts +127 -22
- package/src/templates/agents.ts +17 -4
- package/src/templates/base.ts +22 -4
- package/src/templates/index.ts +18 -1
- package/src/templates/server.ts +58 -1
- package/src/templates/shared.ts +1 -1
package/src/provider-add.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import {
|
|
4
4
|
type ProviderPresetName,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
loadBeignetConfig,
|
|
11
11
|
type ResolvedBeignetConfig,
|
|
12
12
|
} from "./config.js";
|
|
13
|
+
import { aliasModule } from "./make/shared.js";
|
|
13
14
|
import {
|
|
14
15
|
appendToArrayExpression,
|
|
15
16
|
appendToNamedArray,
|
|
@@ -64,6 +65,7 @@ type ProviderPreset = {
|
|
|
64
65
|
docs: string;
|
|
65
66
|
nextSteps: string[];
|
|
66
67
|
conflictingProviderEntries?: string[];
|
|
68
|
+
additionalImports?: string[];
|
|
67
69
|
};
|
|
68
70
|
|
|
69
71
|
type PlannedFile = {
|
|
@@ -169,7 +171,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
169
171
|
displayName: "Inngest jobs",
|
|
170
172
|
importName: "createInngestJobsProvider",
|
|
171
173
|
importFrom: "@beignet/provider-jobs-inngest",
|
|
172
|
-
providerEntry: "createInngestJobsProvider()",
|
|
174
|
+
providerEntry: "createInngestJobsProvider({ client: inngest })",
|
|
173
175
|
dependencies: (packageJson) => ({
|
|
174
176
|
"@beignet/provider-jobs-inngest": beignetDependencyVersion(packageJson),
|
|
175
177
|
inngest: externalVersions.inngest,
|
|
@@ -180,11 +182,20 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
180
182
|
importName: "JobDispatcherPort",
|
|
181
183
|
importFrom: "@beignet/core/ports",
|
|
182
184
|
},
|
|
183
|
-
envVars: [
|
|
185
|
+
envVars: [
|
|
186
|
+
"INNGEST_APP_NAME",
|
|
187
|
+
"INNGEST_DEV",
|
|
188
|
+
"INNGEST_EVENT_KEY",
|
|
189
|
+
"INNGEST_SIGNING_KEY",
|
|
190
|
+
"INNGEST_SIGNING_KEY_FALLBACK",
|
|
191
|
+
],
|
|
184
192
|
envExample: [
|
|
185
193
|
"# Inngest jobs provider",
|
|
186
194
|
"INNGEST_APP_NAME=beignet-app",
|
|
195
|
+
"INNGEST_DEV=1",
|
|
187
196
|
"# INNGEST_EVENT_KEY=",
|
|
197
|
+
"# INNGEST_SIGNING_KEY=",
|
|
198
|
+
"# INNGEST_SIGNING_KEY_FALLBACK=",
|
|
188
199
|
"",
|
|
189
200
|
].join("\n"),
|
|
190
201
|
docs: [
|
|
@@ -192,16 +203,16 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
192
203
|
"",
|
|
193
204
|
"- Package: `@beignet/provider-jobs-inngest`",
|
|
194
205
|
"- Peer dependency: `inngest`",
|
|
195
|
-
"- The preset
|
|
206
|
+
"- The preset creates one shared Inngest client, a central Beignet job registry, and wires that client into `server/providers.ts`.",
|
|
196
207
|
"- Define jobs with `defineJob(...)` and dispatch them through `ctx.ports.jobs.dispatch(...)`.",
|
|
197
|
-
"-
|
|
208
|
+
"- Next.js apps receive `app/api/inngest/route.ts`; other runtimes receive `createAppInngestFunctions(...)` so their host can bind an app-owned service context.",
|
|
198
209
|
"- Use `ctx.ports.inngest` only as an escape hatch for Inngest-specific workflow APIs; prefer `ctx.ports.jobs.dispatch(...)` for Beignet jobs.",
|
|
199
210
|
"",
|
|
200
211
|
].join("\n"),
|
|
201
212
|
nextSteps: [
|
|
202
213
|
"Run your package manager install command.",
|
|
203
|
-
"
|
|
204
|
-
"
|
|
214
|
+
"Use INNGEST_DEV=1 only with the local dev server. Set INNGEST_EVENT_KEY and INNGEST_SIGNING_KEY in production.",
|
|
215
|
+
"For non-Next runtimes, call createAppInngestFunctions(...) after creating the app server, then mount the result with Inngest's host adapter.",
|
|
205
216
|
"Run beignet provider audit and beignet doctor --strict.",
|
|
206
217
|
],
|
|
207
218
|
conflictingProviderEntries: ["createBullMQJobsProvider"],
|
|
@@ -764,7 +775,12 @@ export async function addProviderPreset(
|
|
|
764
775
|
const config = await loadBeignetConfig(targetDir);
|
|
765
776
|
const readSource: ProviderSourceReader = (filePath) =>
|
|
766
777
|
readOptionalAppFile(targetDir, filePath);
|
|
767
|
-
const filesToPlan = await providerPresetFiles(
|
|
778
|
+
const filesToPlan = await providerPresetFiles(
|
|
779
|
+
preset,
|
|
780
|
+
config,
|
|
781
|
+
readSource,
|
|
782
|
+
targetDir,
|
|
783
|
+
);
|
|
768
784
|
const plannedFiles = await planFiles(filesToPlan, readSource);
|
|
769
785
|
|
|
770
786
|
if (!options.dryRun) {
|
|
@@ -802,7 +818,12 @@ export async function planProviderPresetsForCreate(options: {
|
|
|
802
818
|
|
|
803
819
|
for (const name of options.names) {
|
|
804
820
|
const preset = presets[name];
|
|
805
|
-
const filesToPlan = await providerPresetFiles(
|
|
821
|
+
const filesToPlan = await providerPresetFiles(
|
|
822
|
+
preset,
|
|
823
|
+
config,
|
|
824
|
+
readSource,
|
|
825
|
+
options.targetDir,
|
|
826
|
+
);
|
|
806
827
|
const plannedFiles = await planFiles(filesToPlan, readSource);
|
|
807
828
|
|
|
808
829
|
for (const file of plannedFiles) {
|
|
@@ -827,6 +848,7 @@ async function providerPresetFiles(
|
|
|
827
848
|
preset: ProviderPreset,
|
|
828
849
|
config: ResolvedBeignetConfig,
|
|
829
850
|
readSource: ProviderSourceReader,
|
|
851
|
+
targetDir: string,
|
|
830
852
|
): Promise<ProviderPlanFile[]> {
|
|
831
853
|
const packageJsonPath = "package.json";
|
|
832
854
|
const providersPath = serverProvidersPath(config);
|
|
@@ -840,6 +862,20 @@ async function providerPresetFiles(
|
|
|
840
862
|
await readSource(".env.example"),
|
|
841
863
|
preset,
|
|
842
864
|
);
|
|
865
|
+
const effectivePreset =
|
|
866
|
+
preset.name === "jobs-inngest"
|
|
867
|
+
? {
|
|
868
|
+
...preset,
|
|
869
|
+
additionalImports: [
|
|
870
|
+
`import { inngest } from "${aliasModule(
|
|
871
|
+
path.posix.join(
|
|
872
|
+
directoryPath(path.posix.dirname(config.paths.portWiring)),
|
|
873
|
+
"inngest.ts",
|
|
874
|
+
),
|
|
875
|
+
)}";`,
|
|
876
|
+
],
|
|
877
|
+
}
|
|
878
|
+
: preset;
|
|
843
879
|
const files = [
|
|
844
880
|
{
|
|
845
881
|
path: packageJsonPath,
|
|
@@ -849,7 +885,7 @@ async function providerPresetFiles(
|
|
|
849
885
|
path: providersPath,
|
|
850
886
|
content: updateServerProviders(
|
|
851
887
|
await readRequiredSource(readSource, providersPath),
|
|
852
|
-
|
|
888
|
+
effectivePreset,
|
|
853
889
|
),
|
|
854
890
|
},
|
|
855
891
|
{
|
|
@@ -879,9 +915,167 @@ async function providerPresetFiles(
|
|
|
879
915
|
});
|
|
880
916
|
}
|
|
881
917
|
|
|
918
|
+
if (preset.name === "jobs-inngest") {
|
|
919
|
+
files.push(
|
|
920
|
+
...(await inngestProviderFiles({ targetDir, config, readSource })),
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
return files;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
async function inngestProviderFiles(options: {
|
|
928
|
+
targetDir: string;
|
|
929
|
+
config: ResolvedBeignetConfig;
|
|
930
|
+
readSource: ProviderSourceReader;
|
|
931
|
+
}): Promise<ProviderPlanFile[]> {
|
|
932
|
+
const infrastructurePath = directoryPath(
|
|
933
|
+
path.posix.dirname(options.config.paths.portWiring),
|
|
934
|
+
);
|
|
935
|
+
const serverPath = directoryPath(
|
|
936
|
+
path.posix.dirname(options.config.paths.server),
|
|
937
|
+
);
|
|
938
|
+
const clientPath = path.posix.join(infrastructurePath, "inngest.ts");
|
|
939
|
+
const registryPath = path.posix.join(serverPath, "inngest.ts");
|
|
940
|
+
const jobRegistries = await discoverJobRegistries(
|
|
941
|
+
options.targetDir,
|
|
942
|
+
options.config,
|
|
943
|
+
);
|
|
944
|
+
const imports = jobRegistries
|
|
945
|
+
.map(({ name, file }) => `import { ${name} } from "${aliasModule(file)}";`)
|
|
946
|
+
.join("\n");
|
|
947
|
+
const entries = jobRegistries.map(({ name }) => `\t...${name},`).join("\n");
|
|
948
|
+
|
|
949
|
+
const files: ProviderPlanFile[] = [];
|
|
950
|
+
if ((await options.readSource(clientPath)) === undefined) {
|
|
951
|
+
files.push({
|
|
952
|
+
path: clientPath,
|
|
953
|
+
content: `import { Inngest } from "inngest";
|
|
954
|
+
|
|
955
|
+
/** Shared by the Beignet jobs provider and the Inngest serve endpoint. */
|
|
956
|
+
export const inngest = new Inngest({
|
|
957
|
+
\tid: process.env.INNGEST_APP_NAME ?? "beignet-app",
|
|
958
|
+
});
|
|
959
|
+
`,
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
if ((await options.readSource(registryPath)) === undefined) {
|
|
963
|
+
files.push({
|
|
964
|
+
path: registryPath,
|
|
965
|
+
content:
|
|
966
|
+
options.config.framework === "next"
|
|
967
|
+
? `import { createServiceActor } from "@beignet/core/ports";
|
|
968
|
+
import { createInngestJobFunctions } from "@beignet/provider-jobs-inngest";
|
|
969
|
+
import type { AppContext } from "${aliasModule(options.config.paths.appContext)}";
|
|
970
|
+
import { inngest } from "${aliasModule(clientPath)}";
|
|
971
|
+
${imports ? `${imports}\n` : ""}import { getServer } from "${aliasModule(options.config.paths.server)}";
|
|
972
|
+
|
|
973
|
+
/** Central registry updated by \`beignet make job\`. */
|
|
974
|
+
export const inngestJobs = [
|
|
975
|
+
${entries}
|
|
976
|
+
] as const;
|
|
977
|
+
|
|
978
|
+
export const inngestFunctions = createInngestJobFunctions<AppContext>({
|
|
979
|
+
\tclient: inngest,
|
|
980
|
+
\tjobs: inngestJobs,
|
|
981
|
+
\tctx: async () => {
|
|
982
|
+
\t\tconst server = await getServer();
|
|
983
|
+
\t\treturn server.createServiceContext({
|
|
984
|
+
\t\t\tactor: createServiceActor("beignet-inngest"),
|
|
985
|
+
\t\t});
|
|
986
|
+
\t},
|
|
987
|
+
\tinstrumentation: async () => (await getServer()).ports,
|
|
988
|
+
\terrorReporter: async () => (await getServer()).ports.errorReporter,
|
|
989
|
+
});
|
|
990
|
+
`
|
|
991
|
+
: `import {
|
|
992
|
+
\tcreateInngestJobFunctions,
|
|
993
|
+
\ttype CreateInngestJobFunctionsOptions,
|
|
994
|
+
} from "@beignet/provider-jobs-inngest";
|
|
995
|
+
import type { AppContext } from "${aliasModule(options.config.paths.appContext)}";
|
|
996
|
+
import { inngest } from "${aliasModule(clientPath)}";
|
|
997
|
+
${imports ? `${imports}\n` : ""}
|
|
998
|
+
/** Central registry updated by \`beignet make job\`. */
|
|
999
|
+
export const inngestJobs = [
|
|
1000
|
+
${entries}
|
|
1001
|
+
] as const;
|
|
1002
|
+
|
|
1003
|
+
export type AppInngestFunctionOptions = Omit<
|
|
1004
|
+
\tCreateInngestJobFunctionsOptions<AppContext>,
|
|
1005
|
+
\t"client" | "jobs" | "ctx"
|
|
1006
|
+
> & {
|
|
1007
|
+
\tctx: NonNullable<CreateInngestJobFunctionsOptions<AppContext>["ctx"]>;
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
/** Bind the app-owned runtime context when mounting an Inngest host adapter. */
|
|
1011
|
+
export function createAppInngestFunctions(
|
|
1012
|
+
\toptions: AppInngestFunctionOptions,
|
|
1013
|
+
) {
|
|
1014
|
+
\treturn createInngestJobFunctions<AppContext>({
|
|
1015
|
+
\t\t...options,
|
|
1016
|
+
\t\tclient: inngest,
|
|
1017
|
+
\t\tjobs: inngestJobs,
|
|
1018
|
+
\t});
|
|
1019
|
+
}
|
|
1020
|
+
`,
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
if (options.config.framework === "next") {
|
|
1024
|
+
const routePath = path.posix.join(
|
|
1025
|
+
directoryPath(options.config.paths.routes),
|
|
1026
|
+
"inngest/route.ts",
|
|
1027
|
+
);
|
|
1028
|
+
if ((await options.readSource(routePath)) === undefined) {
|
|
1029
|
+
files.push({
|
|
1030
|
+
path: routePath,
|
|
1031
|
+
content: `import { serve } from "inngest/next";
|
|
1032
|
+
import { inngest } from "${aliasModule(clientPath)}";
|
|
1033
|
+
import { inngestFunctions } from "${aliasModule(registryPath)}";
|
|
1034
|
+
|
|
1035
|
+
export const { GET, POST, PUT } = serve({
|
|
1036
|
+
\tclient: inngest,
|
|
1037
|
+
\tfunctions: inngestFunctions,
|
|
1038
|
+
});
|
|
1039
|
+
`,
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
882
1044
|
return files;
|
|
883
1045
|
}
|
|
884
1046
|
|
|
1047
|
+
async function discoverJobRegistries(
|
|
1048
|
+
targetDir: string,
|
|
1049
|
+
config: ResolvedBeignetConfig,
|
|
1050
|
+
): Promise<Array<{ name: string; file: string }>> {
|
|
1051
|
+
const featuresDir = path.join(
|
|
1052
|
+
targetDir,
|
|
1053
|
+
directoryPath(config.paths.features),
|
|
1054
|
+
);
|
|
1055
|
+
let entries: string[];
|
|
1056
|
+
try {
|
|
1057
|
+
entries = await readdir(featuresDir, { recursive: true, encoding: "utf8" });
|
|
1058
|
+
} catch {
|
|
1059
|
+
return [];
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
const registries: Array<{ name: string; file: string }> = [];
|
|
1063
|
+
for (const entry of entries) {
|
|
1064
|
+
const normalized = entry.replaceAll(path.sep, "/");
|
|
1065
|
+
if (!normalized.endsWith("/jobs/index.ts")) continue;
|
|
1066
|
+
const absolutePath = path.join(featuresDir, entry);
|
|
1067
|
+
const source = await readFile(absolutePath, "utf8");
|
|
1068
|
+
const match = source.match(/export const\s+([A-Za-z_$][\w$]*Jobs)\s*=/);
|
|
1069
|
+
if (!match?.[1]) continue;
|
|
1070
|
+
registries.push({
|
|
1071
|
+
name: match[1],
|
|
1072
|
+
file: path.posix.join(directoryPath(config.paths.features), normalized),
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
return registries.sort((left, right) => left.file.localeCompare(right.file));
|
|
1077
|
+
}
|
|
1078
|
+
|
|
885
1079
|
function providerPresetResult(
|
|
886
1080
|
preset: ProviderPreset,
|
|
887
1081
|
targetDir: string,
|
|
@@ -982,9 +1176,14 @@ function updatePackageJson(
|
|
|
982
1176
|
function updateServerProviders(source: string, preset: ProviderPreset): string {
|
|
983
1177
|
assertNoConflictingProviders(source, preset);
|
|
984
1178
|
|
|
1179
|
+
let next = source;
|
|
1180
|
+
for (const importLine of preset.additionalImports ?? []) {
|
|
1181
|
+
if (!next.includes(importLine)) next = insertAfterImports(next, importLine);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
985
1184
|
const importLine = `import { ${preset.importName} } from "${preset.importFrom}";`;
|
|
986
1185
|
const result = appendToNamedArray(
|
|
987
|
-
|
|
1186
|
+
next,
|
|
988
1187
|
"providers",
|
|
989
1188
|
preset.providerEntry,
|
|
990
1189
|
hasNamedImport(source, preset.importName, preset.importFrom)
|
|
@@ -998,7 +1197,7 @@ function updateServerProviders(source: string, preset: ProviderPreset): string {
|
|
|
998
1197
|
);
|
|
999
1198
|
}
|
|
1000
1199
|
|
|
1001
|
-
return result.kind === "updated" ? result.source :
|
|
1200
|
+
return result.kind === "updated" ? result.source : next;
|
|
1002
1201
|
}
|
|
1003
1202
|
|
|
1004
1203
|
function assertNoConflictingProviders(
|
package/src/provider-audit.ts
CHANGED
|
@@ -59,6 +59,12 @@ export type ProviderRegistrationStatus =
|
|
|
59
59
|
| "optional-missing"
|
|
60
60
|
| "not-required";
|
|
61
61
|
|
|
62
|
+
export type ProviderRegistrationSource = {
|
|
63
|
+
file: string;
|
|
64
|
+
line: number;
|
|
65
|
+
column: number;
|
|
66
|
+
};
|
|
67
|
+
|
|
62
68
|
export type ProviderMetadataAudit =
|
|
63
69
|
| {
|
|
64
70
|
status: "valid";
|
|
@@ -102,6 +108,7 @@ export type ProviderAuditEntry = {
|
|
|
102
108
|
declared: boolean;
|
|
103
109
|
metadata: ProviderMetadataAudit;
|
|
104
110
|
registration: ProviderRegistrationStatus;
|
|
111
|
+
registrationSources: ProviderRegistrationSource[];
|
|
105
112
|
requiredEnv: ProviderRequirementAudit;
|
|
106
113
|
requiredTables: ProviderRequirementAudit;
|
|
107
114
|
appPorts: ProviderAppPortsAudit;
|
|
@@ -138,12 +145,15 @@ export async function auditProviders(
|
|
|
138
145
|
const packageJson = await readPackageJson(targetDir, files);
|
|
139
146
|
const packageNames = await providerAuditPackageNames(targetDir, packageJson);
|
|
140
147
|
const sourceCache = new Map<string, string>();
|
|
141
|
-
const
|
|
148
|
+
const providerEntryRecords = await readProviderListEntryRecords(
|
|
142
149
|
targetDir,
|
|
143
150
|
files,
|
|
144
151
|
config,
|
|
145
152
|
sourceCache,
|
|
146
153
|
);
|
|
154
|
+
const providerEntries = providerEntryRecords.flatMap(
|
|
155
|
+
(entry) => entry.searchableText,
|
|
156
|
+
);
|
|
147
157
|
const configFiles = providerOperationalConfigFiles(files, config);
|
|
148
158
|
const portsSource = files.includes(config.paths.ports)
|
|
149
159
|
? await readCachedSource(targetDir, config.paths.ports, sourceCache)
|
|
@@ -191,6 +201,7 @@ export async function auditProviders(
|
|
|
191
201
|
sourceCache,
|
|
192
202
|
portsSource,
|
|
193
203
|
providerEntries,
|
|
204
|
+
providerEntryRecords,
|
|
194
205
|
sourceFile: source.file,
|
|
195
206
|
packageJson,
|
|
196
207
|
rule: providerDoctorRuleFromMetadata(packageName, parsed.metadata),
|
|
@@ -356,20 +367,47 @@ export async function readProviderListEntries(
|
|
|
356
367
|
config: ResolvedBeignetConfig,
|
|
357
368
|
sourceCache: Map<string, string>,
|
|
358
369
|
): Promise<string[]> {
|
|
370
|
+
const records = await readProviderListEntryRecords(
|
|
371
|
+
targetDir,
|
|
372
|
+
files,
|
|
373
|
+
config,
|
|
374
|
+
sourceCache,
|
|
375
|
+
);
|
|
376
|
+
return records.flatMap((entry) => entry.searchableText);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
type ProviderListEntryRecord = {
|
|
380
|
+
searchableText: string[];
|
|
381
|
+
source: ProviderRegistrationSource;
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
async function readProviderListEntryRecords(
|
|
385
|
+
targetDir: string,
|
|
386
|
+
files: string[],
|
|
387
|
+
config: ResolvedBeignetConfig,
|
|
388
|
+
sourceCache: Map<string, string>,
|
|
389
|
+
): Promise<ProviderListEntryRecord[]> {
|
|
359
390
|
const providerFiles = serverProviderFiles(files, config);
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
391
|
+
const records: ProviderListEntryRecord[] = [];
|
|
392
|
+
for (const file of providerFiles) {
|
|
393
|
+
const providerSource = await readCachedSource(targetDir, file, sourceCache);
|
|
394
|
+
for (const list of extractProviderLists(providerSource)) {
|
|
395
|
+
for (const entry of providerListEntries(list.source)) {
|
|
396
|
+
records.push({
|
|
397
|
+
searchableText: expandProviderEntryIdentifiers(
|
|
398
|
+
[entry.source],
|
|
399
|
+
providerSource,
|
|
400
|
+
),
|
|
401
|
+
source: sourceLocation(
|
|
402
|
+
file,
|
|
403
|
+
providerSource,
|
|
404
|
+
list.start + entry.start,
|
|
405
|
+
),
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return records;
|
|
373
411
|
}
|
|
374
412
|
|
|
375
413
|
/**
|
|
@@ -1128,6 +1166,7 @@ async function providerAuditEntryForRule(options: {
|
|
|
1128
1166
|
sourceCache: Map<string, string>;
|
|
1129
1167
|
portsSource: string;
|
|
1130
1168
|
providerEntries: string[];
|
|
1169
|
+
providerEntryRecords: ProviderListEntryRecord[];
|
|
1131
1170
|
sourceFile: string;
|
|
1132
1171
|
packageJson: ProviderPackageJson | undefined;
|
|
1133
1172
|
rule: ProviderDoctorRule;
|
|
@@ -1201,6 +1240,9 @@ async function providerAuditEntryForRule(options: {
|
|
|
1201
1240
|
requiredTables,
|
|
1202
1241
|
appPorts,
|
|
1203
1242
|
});
|
|
1243
|
+
const registrationTokens =
|
|
1244
|
+
options.rule.variants?.flatMap((variant) => variant.tokens) ??
|
|
1245
|
+
options.rule.tokens;
|
|
1204
1246
|
|
|
1205
1247
|
return {
|
|
1206
1248
|
packageName: options.rule.packageName,
|
|
@@ -1214,6 +1256,13 @@ async function providerAuditEntryForRule(options: {
|
|
|
1214
1256
|
issues: [],
|
|
1215
1257
|
},
|
|
1216
1258
|
registration,
|
|
1259
|
+
registrationSources:
|
|
1260
|
+
registration === "registered"
|
|
1261
|
+
? registrationSourcesForTokens(
|
|
1262
|
+
registrationTokens,
|
|
1263
|
+
options.providerEntryRecords,
|
|
1264
|
+
)
|
|
1265
|
+
: [],
|
|
1217
1266
|
requiredEnv,
|
|
1218
1267
|
requiredTables,
|
|
1219
1268
|
appPorts,
|
|
@@ -1294,6 +1343,7 @@ function providerAuditEntryForMissingMetadata(
|
|
|
1294
1343
|
declared: installedPackageNames(packageJson).has(packageName),
|
|
1295
1344
|
metadata: { status: "missing", issues: [] },
|
|
1296
1345
|
registration: "not-required",
|
|
1346
|
+
registrationSources: [],
|
|
1297
1347
|
requiredEnv: emptyRequirement(),
|
|
1298
1348
|
requiredTables: emptyRequirement(),
|
|
1299
1349
|
appPorts: emptyAppPorts(),
|
|
@@ -1319,6 +1369,7 @@ function providerAuditEntryForInvalidMetadata(
|
|
|
1319
1369
|
issues: [...issues],
|
|
1320
1370
|
},
|
|
1321
1371
|
registration: "not-required",
|
|
1372
|
+
registrationSources: [],
|
|
1322
1373
|
requiredEnv: emptyRequirement(),
|
|
1323
1374
|
requiredTables: emptyRequirement(),
|
|
1324
1375
|
appPorts: emptyAppPorts(),
|
|
@@ -1345,6 +1396,28 @@ function registrationAudit(
|
|
|
1345
1396
|
return "not-required";
|
|
1346
1397
|
}
|
|
1347
1398
|
|
|
1399
|
+
function registrationSourcesForTokens(
|
|
1400
|
+
tokens: readonly string[],
|
|
1401
|
+
entries: readonly ProviderListEntryRecord[],
|
|
1402
|
+
): ProviderRegistrationSource[] {
|
|
1403
|
+
const sources = entries
|
|
1404
|
+
.filter((entry) =>
|
|
1405
|
+
tokens.some((token) =>
|
|
1406
|
+
entry.searchableText.some((text) => text.includes(token)),
|
|
1407
|
+
),
|
|
1408
|
+
)
|
|
1409
|
+
.map((entry) => entry.source);
|
|
1410
|
+
return sources.filter(
|
|
1411
|
+
(source, index) =>
|
|
1412
|
+
sources.findIndex(
|
|
1413
|
+
(candidate) =>
|
|
1414
|
+
candidate.file === source.file &&
|
|
1415
|
+
candidate.line === source.line &&
|
|
1416
|
+
candidate.column === source.column,
|
|
1417
|
+
) === index,
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1348
1421
|
function variantRegistrationAudit(
|
|
1349
1422
|
variants: readonly ProviderDoctorVariantRule[],
|
|
1350
1423
|
detectedVariants: readonly ProviderDoctorVariantRule[],
|
|
@@ -1572,18 +1645,25 @@ export function serverProviderFiles(
|
|
|
1572
1645
|
return candidates.filter((file) => files.includes(file));
|
|
1573
1646
|
}
|
|
1574
1647
|
|
|
1575
|
-
function
|
|
1648
|
+
function extractProviderLists(
|
|
1649
|
+
source: string,
|
|
1650
|
+
): { source: string; start: number }[] {
|
|
1576
1651
|
const listMatches = [
|
|
1577
1652
|
...source.matchAll(/\bproviders\s*=\s*\[([\s\S]*?)\]\s*(?:;|as const)/g),
|
|
1578
1653
|
...source.matchAll(/\bproviders\s*:\s*\[([\s\S]*?)\]/g),
|
|
1579
|
-
];
|
|
1580
|
-
|
|
1581
|
-
|
|
1654
|
+
].sort((left, right) => (left.index ?? 0) - (right.index ?? 0));
|
|
1655
|
+
return listMatches.map((match) => ({
|
|
1656
|
+
source: match[1] ?? "",
|
|
1657
|
+
start: (match.index ?? 0) + match[0].indexOf("[") + 1,
|
|
1658
|
+
}));
|
|
1582
1659
|
}
|
|
1583
1660
|
|
|
1584
|
-
function providerListEntries(
|
|
1585
|
-
|
|
1661
|
+
function providerListEntries(
|
|
1662
|
+
source: string,
|
|
1663
|
+
): { source: string; start: number }[] {
|
|
1664
|
+
const entries: { source: string; start: number }[] = [];
|
|
1586
1665
|
let current = "";
|
|
1666
|
+
let currentStart: number | undefined;
|
|
1587
1667
|
let depth = 0;
|
|
1588
1668
|
let quote: '"' | "'" | "`" | undefined;
|
|
1589
1669
|
let escaped = false;
|
|
@@ -1611,6 +1691,9 @@ function providerListEntries(source: string): string[] {
|
|
|
1611
1691
|
}
|
|
1612
1692
|
|
|
1613
1693
|
if (quote) {
|
|
1694
|
+
if (currentStart === undefined && !/\s/.test(char)) {
|
|
1695
|
+
currentStart = index;
|
|
1696
|
+
}
|
|
1614
1697
|
current += char;
|
|
1615
1698
|
if (escaped) {
|
|
1616
1699
|
escaped = false;
|
|
@@ -1640,10 +1723,15 @@ function providerListEntries(source: string): string[] {
|
|
|
1640
1723
|
|
|
1641
1724
|
if (char === '"' || char === "'" || char === "`") {
|
|
1642
1725
|
quote = char;
|
|
1726
|
+
currentStart ??= index;
|
|
1643
1727
|
current += char;
|
|
1644
1728
|
continue;
|
|
1645
1729
|
}
|
|
1646
1730
|
|
|
1731
|
+
if (currentStart === undefined && !/\s/.test(char)) {
|
|
1732
|
+
currentStart = index;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1647
1735
|
if (char === "(" || char === "[" || char === "{") {
|
|
1648
1736
|
depth += 1;
|
|
1649
1737
|
} else if (char === ")" || char === "]" || char === "}") {
|
|
@@ -1652,8 +1740,9 @@ function providerListEntries(source: string): string[] {
|
|
|
1652
1740
|
|
|
1653
1741
|
if (char === "," && depth === 0) {
|
|
1654
1742
|
const entry = current.trim();
|
|
1655
|
-
if (entry) entries.push(entry);
|
|
1743
|
+
if (entry) entries.push({ source: entry, start: currentStart ?? index });
|
|
1656
1744
|
current = "";
|
|
1745
|
+
currentStart = undefined;
|
|
1657
1746
|
continue;
|
|
1658
1747
|
}
|
|
1659
1748
|
|
|
@@ -1661,10 +1750,26 @@ function providerListEntries(source: string): string[] {
|
|
|
1661
1750
|
}
|
|
1662
1751
|
|
|
1663
1752
|
const entry = current.trim();
|
|
1664
|
-
if (entry)
|
|
1753
|
+
if (entry) {
|
|
1754
|
+
entries.push({ source: entry, start: currentStart ?? source.length });
|
|
1755
|
+
}
|
|
1665
1756
|
return entries;
|
|
1666
1757
|
}
|
|
1667
1758
|
|
|
1759
|
+
function sourceLocation(
|
|
1760
|
+
file: string,
|
|
1761
|
+
source: string,
|
|
1762
|
+
index: number,
|
|
1763
|
+
): ProviderRegistrationSource {
|
|
1764
|
+
const prefix = source.slice(0, index);
|
|
1765
|
+
const lastNewline = prefix.lastIndexOf("\n");
|
|
1766
|
+
return {
|
|
1767
|
+
file,
|
|
1768
|
+
line: prefix.split("\n").length,
|
|
1769
|
+
column: index - lastNewline,
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1668
1773
|
function metadataSourcePath(targetDir: string, file: string): string {
|
|
1669
1774
|
const targetRelative = normalizePath(path.relative(targetDir, file));
|
|
1670
1775
|
if (!targetRelative.startsWith("../")) return targetRelative;
|
package/src/templates/agents.ts
CHANGED
|
@@ -107,6 +107,10 @@ formatting. The individual commands (\`${lint}\`, \`${cli} lint\`,
|
|
|
107
107
|
\`${cli} doctor --strict\`, \`${test}\`, \`${typecheck}\`) still work when you
|
|
108
108
|
need one check alone.
|
|
109
109
|
|
|
110
|
+
Before exploring unfamiliar code, run \`${cli} map\` for the human inventory
|
|
111
|
+
or \`${cli} map --json --feature <name>\` for an evidence-backed graph scoped
|
|
112
|
+
to one feature.
|
|
113
|
+
|
|
110
114
|
## Package skills
|
|
111
115
|
|
|
112
116
|
This app trusts Beignet's package-shipped agent skills through
|
|
@@ -142,10 +146,19 @@ skill-loading block.
|
|
|
142
146
|
## MCP server
|
|
143
147
|
|
|
144
148
|
\`.mcp.json\` registers the app-local \`@beignet/cli\` bin at
|
|
145
|
-
\`./node_modules/.bin/beignet mcp\`, which exposes
|
|
146
|
-
structured tools named exactly: \`
|
|
147
|
-
\`
|
|
148
|
-
|
|
149
|
+
\`./node_modules/.bin/beignet mcp\`, which exposes the app map, validation, and
|
|
150
|
+
generators as structured tools named exactly: \`app_map\`, \`explain\`,
|
|
151
|
+
\`check\`, \`routes\`, \`doctor\`, \`doctor_fix\`, \`lint\`, \`make\`,
|
|
152
|
+
\`provider_add\`. Use \`app_map\` before broad code search and pass a feature
|
|
153
|
+
or node kinds to keep context focused. Use \`explain\` for source-backed
|
|
154
|
+
relationships, conventions, findings, and suggested files for one feature,
|
|
155
|
+
route, provider, or diagnostic. Use \`check\` after edits to run the complete
|
|
156
|
+
validation loop; it returns the same versioned result as
|
|
157
|
+
\`${cli} check --json\`, bounds failure output, and cancels the active package
|
|
158
|
+
script when the MCP request is cancelled. It does not apply Beignet fixes, but
|
|
159
|
+
app-owned scripts retain their normal side effects. Clients that do not read
|
|
160
|
+
\`.mcp.json\` can use the same command from the app root; use \`${cli} mcp\`
|
|
161
|
+
only for terminal debugging.
|
|
149
162
|
`;
|
|
150
163
|
}
|
|
151
164
|
|