@beignet/cli 0.0.39 → 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.
Files changed (112) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +110 -16
  3. package/dist/analysis/source-index.d.ts +38 -0
  4. package/dist/analysis/source-index.d.ts.map +1 -0
  5. package/dist/analysis/source-index.js +271 -0
  6. package/dist/analysis/source-index.js.map +1 -0
  7. package/dist/analysis/workspace.d.ts +16 -0
  8. package/dist/analysis/workspace.d.ts.map +1 -0
  9. package/dist/analysis/workspace.js +134 -0
  10. package/dist/analysis/workspace.js.map +1 -0
  11. package/dist/app-map-schema.d.ts +5 -0
  12. package/dist/app-map-schema.d.ts.map +1 -0
  13. package/dist/app-map-schema.js +26 -0
  14. package/dist/app-map-schema.js.map +1 -0
  15. package/dist/app-map.d.ts +96 -0
  16. package/dist/app-map.d.ts.map +1 -0
  17. package/dist/app-map.js +1141 -0
  18. package/dist/app-map.js.map +1 -0
  19. package/dist/check.d.ts +7 -0
  20. package/dist/check.d.ts.map +1 -1
  21. package/dist/check.js +20 -5
  22. package/dist/check.js.map +1 -1
  23. package/dist/config.d.ts +1 -1
  24. package/dist/config.d.ts.map +1 -1
  25. package/dist/config.js +4 -2
  26. package/dist/config.js.map +1 -1
  27. package/dist/db.d.ts +32 -7
  28. package/dist/db.d.ts.map +1 -1
  29. package/dist/db.js +182 -14
  30. package/dist/db.js.map +1 -1
  31. package/dist/explain.d.ts +98 -0
  32. package/dist/explain.d.ts.map +1 -0
  33. package/dist/explain.js +512 -0
  34. package/dist/explain.js.map +1 -0
  35. package/dist/framework.d.ts +3 -0
  36. package/dist/framework.d.ts.map +1 -0
  37. package/dist/framework.js +6 -0
  38. package/dist/framework.js.map +1 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +79 -2
  41. package/dist/index.js.map +1 -1
  42. package/dist/inspect.d.ts +4 -2
  43. package/dist/inspect.d.ts.map +1 -1
  44. package/dist/inspect.js +173 -36
  45. package/dist/inspect.js.map +1 -1
  46. package/dist/lib.d.ts +4 -0
  47. package/dist/lib.d.ts.map +1 -1
  48. package/dist/lib.js +2 -0
  49. package/dist/lib.js.map +1 -1
  50. package/dist/make/payments.d.ts.map +1 -1
  51. package/dist/make/payments.js +2 -0
  52. package/dist/make/payments.js.map +1 -1
  53. package/dist/make.d.ts.map +1 -1
  54. package/dist/make.js +157 -3
  55. package/dist/make.js.map +1 -1
  56. package/dist/mcp.d.ts +1 -1
  57. package/dist/mcp.d.ts.map +1 -1
  58. package/dist/mcp.js +69 -1
  59. package/dist/mcp.js.map +1 -1
  60. package/dist/preflight.d.ts.map +1 -1
  61. package/dist/preflight.js +10 -0
  62. package/dist/preflight.js.map +1 -1
  63. package/dist/provider-add.d.ts.map +1 -1
  64. package/dist/provider-add.js +167 -13
  65. package/dist/provider-add.js.map +1 -1
  66. package/dist/provider-audit.d.ts +6 -0
  67. package/dist/provider-audit.d.ts.map +1 -1
  68. package/dist/provider-audit.js +63 -15
  69. package/dist/provider-audit.js.map +1 -1
  70. package/dist/templates/agents.d.ts.map +1 -1
  71. package/dist/templates/agents.js +17 -4
  72. package/dist/templates/agents.js.map +1 -1
  73. package/dist/templates/base.d.ts.map +1 -1
  74. package/dist/templates/base.js +10 -4
  75. package/dist/templates/base.js.map +1 -1
  76. package/dist/templates/index.d.ts +1 -1
  77. package/dist/templates/index.d.ts.map +1 -1
  78. package/dist/templates/index.js +8 -1
  79. package/dist/templates/index.js.map +1 -1
  80. package/dist/templates/server.d.ts +3 -0
  81. package/dist/templates/server.d.ts.map +1 -1
  82. package/dist/templates/server.js +55 -1
  83. package/dist/templates/server.js.map +1 -1
  84. package/dist/templates/shadcn.js +1 -1
  85. package/dist/templates/shared.js +1 -1
  86. package/package.json +2 -2
  87. package/skills/app-structure/SKILL.md +38 -5
  88. package/src/analysis/source-index.ts +395 -0
  89. package/src/analysis/workspace.ts +180 -0
  90. package/src/app-map-schema.ts +28 -0
  91. package/src/app-map.ts +1705 -0
  92. package/src/check.ts +27 -4
  93. package/src/config.ts +7 -3
  94. package/src/db.ts +232 -14
  95. package/src/explain.ts +786 -0
  96. package/src/framework.ts +13 -0
  97. package/src/index.ts +113 -2
  98. package/src/inspect.ts +259 -40
  99. package/src/lib.ts +36 -0
  100. package/src/make/payments.ts +7 -0
  101. package/src/make.ts +232 -2
  102. package/src/mcp.ts +107 -1
  103. package/src/preflight.ts +14 -0
  104. package/src/provider-add.ts +211 -12
  105. package/src/provider-audit.ts +127 -22
  106. package/src/templates/agents.ts +17 -4
  107. package/src/templates/base.ts +22 -4
  108. package/src/templates/index.ts +18 -1
  109. package/src/templates/server.ts +58 -1
  110. package/src/templates/shadcn.ts +1 -1
  111. package/src/templates/shared.ts +1 -1
  112. package/src/test-helpers/generated-app.ts +1 -1
package/src/lib.ts CHANGED
@@ -6,9 +6,45 @@
6
6
  * loads command modules lazily.
7
7
  */
8
8
 
9
+ export type {
10
+ AppMapEdge,
11
+ AppMapEdgeKind,
12
+ AppMapNode,
13
+ AppMapNodeKind,
14
+ AppMapNodeStatus,
15
+ AppMapOptions,
16
+ AppMapProjectionOptions,
17
+ AppMapResult,
18
+ AppMapSource,
19
+ AppMapSummary,
20
+ AppMapUnresolvedReference,
21
+ } from "./app-map.js";
22
+ export {
23
+ appMapNodeKinds,
24
+ formatAppMap,
25
+ mapApp,
26
+ projectAppMap,
27
+ } from "./app-map.js";
9
28
  export type { CreateOptions } from "./create.js";
10
29
  export { createProject } from "./create.js";
11
30
  export { runDatabaseCommand } from "./db.js";
31
+ export type {
32
+ ExplainAppOptions,
33
+ ExplainAppResult,
34
+ ExplainEvidence,
35
+ ExplainFinding,
36
+ ExplainRelatedNode,
37
+ ExplainRelationship,
38
+ ExplainSuggestedAction,
39
+ ExplainSuggestedFile,
40
+ ExplainTarget,
41
+ ExplainTargetKind,
42
+ } from "./explain.js";
43
+ export {
44
+ explainApp,
45
+ explainTargetKinds,
46
+ formatExplain,
47
+ } from "./explain.js";
12
48
  export { main } from "./index.js";
13
49
  export {
14
50
  applyDoctorFixes,
@@ -6,6 +6,7 @@ import {
6
6
  type ResolvedBeignetConfig,
7
7
  resolveConfig,
8
8
  } from "../config.js";
9
+ import { assertNextFramework } from "../framework.js";
9
10
  import { appendToNamedArray, insertAfterImports } from "../registry-edits.js";
10
11
  import { testSupportTemplateFiles } from "../templates/testing.js";
11
12
  import {
@@ -71,6 +72,12 @@ export async function makePayments(
71
72
  ? resolveConfig(options.config)
72
73
  : await loadBeignetConfig(targetDir);
73
74
 
75
+ assertNextFramework(
76
+ config,
77
+ "beignet make payments",
78
+ "Next.js App Router billing and webhook handlers",
79
+ );
80
+
74
81
  await assertStandardApp(targetDir, config);
75
82
 
76
83
  const persistence = await detectResourcePersistence(targetDir, config);
package/src/make.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  type ResolvedBeignetConfig,
12
12
  resolveConfig,
13
13
  } from "./config.js";
14
+ import { assertNextFramework } from "./framework.js";
14
15
  import {
15
16
  adapterFilePath,
16
17
  addNamedImport,
@@ -143,6 +144,7 @@ import {
143
144
  } from "./make/shared.js";
144
145
  import {
145
146
  appendToArrayExpression,
147
+ appendToNamedArray,
146
148
  appendToOutboxRegistryArray,
147
149
  arrayInitializerInfo,
148
150
  identifiersFromArrayExpression,
@@ -1088,6 +1090,7 @@ export async function makeJob(options: MakeJobOptions): Promise<MakeJobResult> {
1088
1090
  await updateOutboxRegistry(targetDir, names, "jobs", config, {
1089
1091
  dryRun: true,
1090
1092
  });
1093
+ await updateInngestJobRegistry(targetDir, names, config, { dryRun: true });
1091
1094
 
1092
1095
  const result = await makeFeatureArtifact({
1093
1096
  targetDir,
@@ -1114,6 +1117,9 @@ export async function makeJob(options: MakeJobOptions): Promise<MakeJobResult> {
1114
1117
  await applyOutboxPortWiring(result, targetDir, config, {
1115
1118
  dryRun: Boolean(options.dryRun),
1116
1119
  });
1120
+ await applyInngestJobRegistryUpdate(result, targetDir, names, config, {
1121
+ dryRun: Boolean(options.dryRun),
1122
+ });
1117
1123
 
1118
1124
  return result;
1119
1125
  }
@@ -1499,6 +1505,14 @@ export async function makeSchedule(
1499
1505
  ? resolveConfig(options.config)
1500
1506
  : await loadBeignetConfig(targetDir);
1501
1507
 
1508
+ if (options.route) {
1509
+ assertNextFramework(
1510
+ config,
1511
+ "beignet make schedule --route",
1512
+ "a Next.js App Router cron handler",
1513
+ );
1514
+ }
1515
+
1502
1516
  await assertFeatureArtifactApp(targetDir, config, "schedule", {
1503
1517
  requireServer: Boolean(options.route),
1504
1518
  });
@@ -1714,6 +1728,9 @@ async function updateOutboxPortWiring(
1714
1728
  path.join(infrastructureDir(config), "db/provider.ts"),
1715
1729
  );
1716
1730
  }
1731
+ if (await updateOutboxDrainProvider(targetDir, config, options)) {
1732
+ changes.updatedFiles.push(providersFilePath(config));
1733
+ }
1717
1734
  if (await updateOutboxRepositoriesReturnType(targetDir, config, options)) {
1718
1735
  changes.updatedFiles.push(drizzleRepositoriesPath(config));
1719
1736
  }
@@ -1825,6 +1842,31 @@ async function updateOutboxInfrastructurePorts(
1825
1842
  return true;
1826
1843
  }
1827
1844
 
1845
+ async function projectSupportsNextAfter(targetDir: string): Promise<boolean> {
1846
+ const packageJson = JSON.parse(
1847
+ await readFile(path.join(targetDir, "package.json"), "utf8"),
1848
+ ) as PackageJsonLike;
1849
+ const version =
1850
+ packageJson.dependencies?.next ?? packageJson.devDependencies?.next;
1851
+ if (!version) return false;
1852
+
1853
+ return version.split("||").every((range) => {
1854
+ const lowerBound =
1855
+ /^\s*(\^|~|>=|>|=|<=|<)?\s*v?(\d+)(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?([+-][0-9A-Za-z.-]+)?(?=\s|$)/.exec(
1856
+ range,
1857
+ );
1858
+ if (!lowerBound) return false;
1859
+
1860
+ const operator = lowerBound[1];
1861
+ if (operator === "<" || operator === "<=") return false;
1862
+ if (lowerBound[5]?.startsWith("-")) return false;
1863
+
1864
+ const major = Number(lowerBound[2]);
1865
+ const minor = /^\d+$/.test(lowerBound[3] ?? "") ? Number(lowerBound[3]) : 0;
1866
+ return major > 15 || (major === 15 && minor >= 1);
1867
+ });
1868
+ }
1869
+
1828
1870
  async function updateOutboxDatabaseProvider(
1829
1871
  targetDir: string,
1830
1872
  config: ResolvedBeignetConfig,
@@ -1923,6 +1965,95 @@ async function updateOutboxDatabaseProvider(
1923
1965
  return true;
1924
1966
  }
1925
1967
 
1968
+ async function updateOutboxDrainProvider(
1969
+ targetDir: string,
1970
+ config: ResolvedBeignetConfig,
1971
+ options: { dryRun: boolean },
1972
+ ): Promise<boolean> {
1973
+ if (!(await projectSupportsNextAfter(targetDir))) return false;
1974
+
1975
+ const file = providersFilePath(config);
1976
+ const filePath = path.join(targetDir, file);
1977
+ const original = await readOptionalFile(filePath);
1978
+ if (original === undefined) {
1979
+ throw new Error(
1980
+ `Could not find the generated providers file ${file}. Register the outbox drain trigger manually, or restore the generated providers file before running make outbox.`,
1981
+ );
1982
+ }
1983
+
1984
+ let next = addNamedImport(
1985
+ original,
1986
+ "createObservedUnitOfWork",
1987
+ "@beignet/core/ports",
1988
+ );
1989
+ next = addNamedImport(next, "createProvider", "@beignet/core/providers");
1990
+ next = addNamedImport(next, "createNextOutboxDrainTrigger", "@beignet/next");
1991
+ next = addNamedImport(next, "after", "next/server");
1992
+ next = addNamedTypeImport(
1993
+ next,
1994
+ "AppContext",
1995
+ aliasModule(config.paths.appContext),
1996
+ );
1997
+ next = addNamedTypeImport(next, "AppPorts", aliasModule(config.paths.ports));
1998
+ next = addNamedTypeImport(
1999
+ next,
2000
+ "AppServiceContextInput",
2001
+ aliasModule(path.join(path.dirname(config.paths.server), "context.ts")),
2002
+ );
2003
+
2004
+ const providerName = "outboxDrainProvider";
2005
+ if (!new RegExp(`\\bconst\\s+${providerName}\\b`).test(next)) {
2006
+ const outboxModule = aliasModule(config.paths.outbox);
2007
+ const providerDefinition = `const ${providerName} = createProvider<
2008
+ \tPick<AppPorts, "uow">,
2009
+ \tAppContext,
2010
+ \tAppServiceContextInput
2011
+ >()({
2012
+ \tname: "outbox-drain-trigger",
2013
+ \tsetup({ ports, createServiceContext }): { ports: Pick<AppPorts, "uow"> } {
2014
+ \t\tconst triggerOutboxDrain: () => void = createNextOutboxDrainTrigger({
2015
+ \t\t\tdefer: after,
2016
+ \t\t\tcreateContext: () => createServiceContext(undefined),
2017
+ \t\t\tregistry: async () => (await import("${outboxModule}")).outboxRegistry,
2018
+ \t\t\tbatchSize: 100,
2019
+ \t\t});
2020
+
2021
+ \t\treturn {
2022
+ \t\t\tports: {
2023
+ \t\t\t\tuow: createObservedUnitOfWork({
2024
+ \t\t\t\t\tunitOfWork: ports.uow,
2025
+ \t\t\t\t\tafterCommit: triggerOutboxDrain,
2026
+ \t\t\t\t}),
2027
+ \t\t\t},
2028
+ \t\t};
2029
+ \t},
2030
+ });
2031
+ `;
2032
+ const withProvider = next.replace(
2033
+ /\nexport const providers = \[/,
2034
+ `\n${providerDefinition}\nexport const providers = [`,
2035
+ );
2036
+ if (withProvider === next) {
2037
+ throw new Error(
2038
+ `Could not find the exported providers array in ${file}. Register outboxDrainProvider manually, or restore the generated providers file before running make outbox.`,
2039
+ );
2040
+ }
2041
+ next = withProvider;
2042
+ }
2043
+
2044
+ const appended = appendToNamedArray(next, "providers", providerName);
2045
+ if (appended.kind === "missing") {
2046
+ throw new Error(
2047
+ `Could not find the exported providers array in ${file}. Register outboxDrainProvider manually, or restore the generated providers file before running make outbox.`,
2048
+ );
2049
+ }
2050
+ if (appended.kind === "updated") next = appended.source;
2051
+
2052
+ if (next === original) return false;
2053
+ if (!options.dryRun) await writeFile(filePath, next);
2054
+ return true;
2055
+ }
2056
+
1926
2057
  async function updateOutboxRepositoriesReturnType(
1927
2058
  targetDir: string,
1928
2059
  config: ResolvedBeignetConfig,
@@ -2068,6 +2199,12 @@ export async function makeUpload(
2068
2199
  ? resolveConfig(options.config)
2069
2200
  : await loadBeignetConfig(targetDir);
2070
2201
 
2202
+ assertNextFramework(
2203
+ config,
2204
+ "beignet make upload",
2205
+ "a Next.js App Router upload handler",
2206
+ );
2207
+
2071
2208
  await assertFeatureArtifactApp(targetDir, config, "upload");
2072
2209
  if (options.ui) {
2073
2210
  await assertUploadUiApp(targetDir, config);
@@ -2168,6 +2305,12 @@ export async function makeOutbox(
2168
2305
  ? resolveConfig(options.config)
2169
2306
  : await loadBeignetConfig(targetDir);
2170
2307
 
2308
+ assertNextFramework(
2309
+ config,
2310
+ "beignet make outbox",
2311
+ "a Next.js App Router recovery drain handler",
2312
+ );
2313
+
2171
2314
  await assertServerRuntimeApp(targetDir, config, "outbox");
2172
2315
  await updateOutboxPortWiring(targetDir, config, { dryRun: true });
2173
2316
 
@@ -2219,6 +2362,9 @@ export async function makeOutbox(
2219
2362
  config.paths.ports,
2220
2363
  config.paths.portWiring,
2221
2364
  path.join(infrastructureDir(config), "db/provider.ts"),
2365
+ ...((await projectSupportsNextAfter(targetDir))
2366
+ ? [providersFilePath(config)]
2367
+ : []),
2222
2368
  drizzleRepositoriesPath(config),
2223
2369
  outboxDrizzleSchemaFilePath(config),
2224
2370
  drizzleSchemaIndexPath(config),
@@ -3672,6 +3818,92 @@ async function applyOutboxRegistryUpdate(
3672
3818
  }
3673
3819
  }
3674
3820
 
3821
+ function inngestRegistryPath(config: ResolvedBeignetConfig): string {
3822
+ return path.posix.join(path.posix.dirname(config.paths.server), "inngest.ts");
3823
+ }
3824
+
3825
+ async function appUsesInngestJobs(targetDir: string): Promise<boolean> {
3826
+ const source = await readOptionalFile(path.join(targetDir, "package.json"));
3827
+ if (source === undefined) return false;
3828
+
3829
+ try {
3830
+ const packageJson = JSON.parse(source) as PackageJsonLike;
3831
+ return Boolean(
3832
+ packageJson.dependencies?.["@beignet/provider-jobs-inngest"] ??
3833
+ packageJson.devDependencies?.["@beignet/provider-jobs-inngest"],
3834
+ );
3835
+ } catch {
3836
+ return false;
3837
+ }
3838
+ }
3839
+
3840
+ async function updateInngestJobRegistry(
3841
+ targetDir: string,
3842
+ names: JobNames,
3843
+ config: ResolvedBeignetConfig,
3844
+ options: { dryRun: boolean },
3845
+ ): Promise<WriteGeneratedFileResult | undefined> {
3846
+ if (!(await appUsesInngestJobs(targetDir))) return undefined;
3847
+
3848
+ const registryPath = inngestRegistryPath(config);
3849
+ const destination = path.join(targetDir, registryPath);
3850
+ const existing = await readOptionalFile(destination);
3851
+ if (existing === undefined) {
3852
+ throw new Error(
3853
+ `@beignet/provider-jobs-inngest is installed, but ${registryPath} is missing. Run \`beignet provider add jobs-inngest\` to create the execution registry before generating jobs.`,
3854
+ );
3855
+ }
3856
+
3857
+ const featureIndexPath = path.join(
3858
+ featureArtifactDir(names, "jobs", config),
3859
+ "index.ts",
3860
+ );
3861
+ const registryName = `${names.featureSingularCamel}Jobs`;
3862
+ const importLine = `import { ${registryName} } from "${aliasModule(featureIndexPath)}";`;
3863
+ let next = existing;
3864
+ if (!next.includes(importLine)) next = insertAfterImports(next, importLine);
3865
+
3866
+ const registry = arrayInitializerInfo(next, "inngestJobs");
3867
+ if (!registry) {
3868
+ throw new Error(
3869
+ `Could not find \`export const inngestJobs = [...]\` in ${registryPath}. Restore the generated registry or register ${registryName} manually.`,
3870
+ );
3871
+ }
3872
+ if (!identifiersFromArrayExpression(registry.text).has(registryName)) {
3873
+ const updatedArray = appendToArrayExpression(registry.text, [
3874
+ `...${registryName}`,
3875
+ ]);
3876
+ next = `${next.slice(0, registry.start)}${updatedArray}${next.slice(
3877
+ registry.end,
3878
+ )}`;
3879
+ }
3880
+
3881
+ if (next === existing) return "skipped";
3882
+ if (!options.dryRun) await writeFile(destination, next);
3883
+ return "updated";
3884
+ }
3885
+
3886
+ async function applyInngestJobRegistryUpdate(
3887
+ result: MakeJobResult,
3888
+ targetDir: string,
3889
+ names: JobNames,
3890
+ config: ResolvedBeignetConfig,
3891
+ options: { dryRun: boolean },
3892
+ ): Promise<void> {
3893
+ const registryResult = await updateInngestJobRegistry(
3894
+ targetDir,
3895
+ names,
3896
+ config,
3897
+ options,
3898
+ );
3899
+ if (registryResult === undefined) return;
3900
+
3901
+ const registryPath = inngestRegistryPath(config);
3902
+ if (registryResult === "updated") result.updatedFiles.push(registryPath);
3903
+ if (registryResult === "skipped") result.skippedFiles.push(registryPath);
3904
+ if (!result.files.includes(registryPath)) result.files.push(registryPath);
3905
+ }
3906
+
3675
3907
  function schemasFile(
3676
3908
  names: ResourceNames,
3677
3909
  mode: ResourceGenerationMode,
@@ -5003,8 +5235,6 @@ export const ${names.jobExportName} = defineJob("${names.jobName}", {
5003
5235
  \tpayload: ${names.payloadSchemaName},
5004
5236
  \tretry: retry.exponential({
5005
5237
  \t\tattempts: 3,
5006
- \t\tinitialDelay: "1s",
5007
- \t\tmaxDelay: "1m",
5008
5238
  \t}),
5009
5239
  \tasync handle({ payload, ctx }) {
5010
5240
  \t\tctx.ports.logger.info("Job handled", {
package/src/mcp.ts CHANGED
@@ -2,7 +2,11 @@ import path from "node:path";
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { z } from "zod";
5
+ import { mapApp, projectAppMap } from "./app-map.js";
6
+ import { appMapNodeKinds } from "./app-map-schema.js";
7
+ import { checkApp } from "./check.js";
5
8
  import { type ProviderPresetName, providerPresetChoices } from "./choices.js";
9
+ import { explainApp, explainTargetKinds } from "./explain.js";
6
10
  import { applyDoctorFixes, inspectApp } from "./inspect.js";
7
11
  import { lintApp } from "./lint.js";
8
12
  import {
@@ -68,6 +72,8 @@ const makeArtifactChoices = [
68
72
  "use-case",
69
73
  ] as const;
70
74
 
75
+ const defaultMcpCheckTimeoutMs = 10 * 60 * 1_000;
76
+
71
77
  type MakeArtifact = (typeof makeArtifactChoices)[number];
72
78
 
73
79
  const makeInputSchema = {
@@ -325,7 +331,7 @@ async function runProviderAddTool(
325
331
  }
326
332
 
327
333
  /**
328
- * Build the Beignet MCP server with routes, doctor, lint, and make tools.
334
+ * Build the Beignet MCP server with app-map, validation, and generator tools.
329
335
  * Every tool resolves against the app directory captured at launch.
330
336
  */
331
337
  export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
@@ -335,6 +341,106 @@ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
335
341
  version: options.version,
336
342
  });
337
343
 
344
+ server.registerTool(
345
+ "app_map",
346
+ {
347
+ description:
348
+ "Return a deterministic graph of this Beignet app: features, contracts, routes, use cases, authorization, workflows, ports, providers, OpenAPI, tests, dependency edges, and validation findings. Project by feature or node kind to keep agent context focused.",
349
+ inputSchema: {
350
+ feature: z
351
+ .string()
352
+ .optional()
353
+ .describe("Project one feature and its direct relationships."),
354
+ kinds: z
355
+ .array(z.enum(appMapNodeKinds))
356
+ .optional()
357
+ .describe("Include only these app-map node kinds."),
358
+ includeDiagnostics: z
359
+ .boolean()
360
+ .optional()
361
+ .describe("Include doctor and lint findings. Defaults to true."),
362
+ },
363
+ annotations: { readOnlyHint: true },
364
+ },
365
+ async (input) =>
366
+ safeToolResult(async () => {
367
+ const result = await mapApp({ cwd, strict: true });
368
+ return jsonResult(
369
+ projectAppMap(result, {
370
+ feature: input.feature,
371
+ kinds: input.kinds,
372
+ includeDiagnostics: input.includeDiagnostics,
373
+ }),
374
+ );
375
+ }),
376
+ );
377
+
378
+ server.registerTool(
379
+ "explain",
380
+ {
381
+ description:
382
+ "Explain one mapped Beignet feature, route, provider, or diagnostic using deterministic source evidence. Returns relationships, relevant conventions, findings, suggested files, and follow-up commands without changing the app.",
383
+ inputSchema: {
384
+ kind: z.enum(explainTargetKinds).describe("Mapped concept to explain."),
385
+ target: z
386
+ .string()
387
+ .min(1)
388
+ .describe(
389
+ "Feature name, METHOD /path route, provider package or port name, or diagnostic code.",
390
+ ),
391
+ },
392
+ annotations: { readOnlyHint: true },
393
+ },
394
+ async (input) =>
395
+ safeToolResult(async () =>
396
+ jsonResult(
397
+ await explainApp({ cwd, kind: input.kind, target: input.target }),
398
+ ),
399
+ ),
400
+ );
401
+
402
+ server.registerTool(
403
+ "check",
404
+ {
405
+ description:
406
+ "Run the full Beignet app validation loop: Beignet lint, strict doctor, and the app's lint, typecheck, and test scripts. Returns the same versioned JSON as beignet check --json, including every step, bounded failure output, durations, and overall ok status. Does not apply Beignet fixes, but app-owned scripts run with their normal side effects.",
407
+ inputSchema: {
408
+ preflight: z
409
+ .boolean()
410
+ .optional()
411
+ .describe(
412
+ "Include the environment preflight check before app package scripts. Defaults to false.",
413
+ ),
414
+ timeoutMs: z
415
+ .number()
416
+ .int()
417
+ .min(1_000)
418
+ .max(60 * 60 * 1_000)
419
+ .optional()
420
+ .describe(
421
+ "Maximum duration for each app package script in milliseconds. Defaults to 600000 (10 minutes).",
422
+ ),
423
+ },
424
+ annotations: {
425
+ readOnlyHint: false,
426
+ destructiveHint: true,
427
+ idempotentHint: false,
428
+ openWorldHint: true,
429
+ },
430
+ },
431
+ async (input, extra) =>
432
+ safeToolResult(async () =>
433
+ jsonResult(
434
+ await checkApp({
435
+ cwd,
436
+ preflight: input.preflight ?? false,
437
+ scriptTimeoutMs: input.timeoutMs ?? defaultMcpCheckTimeoutMs,
438
+ signal: extra.signal,
439
+ }),
440
+ ),
441
+ ),
442
+ );
443
+
338
444
  server.registerTool(
339
445
  "routes",
340
446
  {
package/src/preflight.ts CHANGED
@@ -245,6 +245,20 @@ export async function runPreflight(
245
245
  }
246
246
  }
247
247
 
248
+ const inngestDev = env.INNGEST_DEV?.trim().toLowerCase();
249
+ if (
250
+ env.NODE_ENV === "production" &&
251
+ inngestDev !== undefined &&
252
+ !["", "0", "false"].includes(inngestDev)
253
+ ) {
254
+ findings.push({
255
+ severity: "error",
256
+ code: "BEIGNET_PREFLIGHT_INNGEST_DEV_IN_PRODUCTION",
257
+ message:
258
+ "INNGEST_DEV enables the local Inngest dev server and must be disabled in production.",
259
+ });
260
+ }
261
+
248
262
  const exampleSource = await readFileIfExists(
249
263
  path.join(options.cwd, ".env.example"),
250
264
  );