@beignet/cli 0.0.41 → 0.0.42

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 (58) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +77 -21
  3. package/dist/choices.d.ts +18 -0
  4. package/dist/choices.d.ts.map +1 -1
  5. package/dist/choices.js +35 -0
  6. package/dist/choices.js.map +1 -1
  7. package/dist/db.d.ts +18 -7
  8. package/dist/db.d.ts.map +1 -1
  9. package/dist/db.js +20 -7
  10. package/dist/db.js.map +1 -1
  11. package/dist/doctor-fixes.d.ts +64 -0
  12. package/dist/doctor-fixes.d.ts.map +1 -0
  13. package/dist/doctor-fixes.js +142 -0
  14. package/dist/doctor-fixes.js.map +1 -0
  15. package/dist/explain.d.ts +3 -1
  16. package/dist/explain.d.ts.map +1 -1
  17. package/dist/explain.js +136 -42
  18. package/dist/explain.js.map +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +92 -25
  21. package/dist/index.js.map +1 -1
  22. package/dist/inspect.d.ts +33 -9
  23. package/dist/inspect.d.ts.map +1 -1
  24. package/dist/inspect.js +353 -116
  25. package/dist/inspect.js.map +1 -1
  26. package/dist/lib.d.ts +6 -2
  27. package/dist/lib.d.ts.map +1 -1
  28. package/dist/lib.js +3 -2
  29. package/dist/lib.js.map +1 -1
  30. package/dist/make/shared.js +3 -3
  31. package/dist/make/shared.js.map +1 -1
  32. package/dist/mcp.d.ts.map +1 -1
  33. package/dist/mcp.js +121 -13
  34. package/dist/mcp.js.map +1 -1
  35. package/dist/templates/agents.d.ts.map +1 -1
  36. package/dist/templates/agents.js +26 -10
  37. package/dist/templates/agents.js.map +1 -1
  38. package/dist/templates/base.d.ts.map +1 -1
  39. package/dist/templates/base.js +3 -3
  40. package/dist/templates/base.js.map +1 -1
  41. package/dist/templates/shared.d.ts +2 -1
  42. package/dist/templates/shared.d.ts.map +1 -1
  43. package/dist/templates/shared.js +7 -4
  44. package/dist/templates/shared.js.map +1 -1
  45. package/package.json +3 -2
  46. package/skills/app-structure/SKILL.md +34 -10
  47. package/src/choices.ts +57 -0
  48. package/src/db.ts +45 -15
  49. package/src/doctor-fixes.ts +252 -0
  50. package/src/explain.ts +151 -43
  51. package/src/index.ts +130 -35
  52. package/src/inspect.ts +497 -145
  53. package/src/lib.ts +28 -1
  54. package/src/make/shared.ts +3 -3
  55. package/src/mcp.ts +187 -13
  56. package/src/templates/agents.ts +26 -10
  57. package/src/templates/base.ts +3 -2
  58. package/src/templates/shared.ts +14 -6
package/src/lib.ts CHANGED
@@ -27,7 +27,25 @@ export {
27
27
  } from "./app-map.js";
28
28
  export type { CreateOptions } from "./create.js";
29
29
  export { createProject } from "./create.js";
30
- export { runDatabaseCommand } from "./db.js";
30
+ export type {
31
+ DatabaseCommand,
32
+ DatabaseSchemaDialect,
33
+ DatabaseSchemaTable,
34
+ RunDatabaseCommandOptions,
35
+ RunDatabaseCommandResult,
36
+ SyncDatabaseSchemaOptions,
37
+ SyncDatabaseSchemaResult,
38
+ } from "./db.js";
39
+ export { runDatabaseCommand, syncDatabaseSchema } from "./db.js";
40
+ export type {
41
+ DoctorFixApplyResult,
42
+ DoctorFixFileChange,
43
+ DoctorFixOperation,
44
+ DoctorFixOperationId,
45
+ DoctorFixPlan,
46
+ InspectFix,
47
+ } from "./doctor-fixes.js";
48
+ export { doctorFixOperationIds } from "./doctor-fixes.js";
31
49
  export type {
32
50
  ExplainAppOptions,
33
51
  ExplainAppResult,
@@ -46,12 +64,21 @@ export {
46
64
  formatExplain,
47
65
  } from "./explain.js";
48
66
  export { main } from "./index.js";
67
+ export type {
68
+ ApplyDoctorFixPlanOptions,
69
+ DoctorFixInspectionResult,
70
+ DoctorFixPlanResult,
71
+ } from "./inspect.js";
49
72
  export {
50
73
  applyDoctorFixes,
74
+ applyDoctorFixPlan,
51
75
  formatDoctor,
76
+ formatDoctorFixPlan,
77
+ formatDoctorFixPlanGithub,
52
78
  formatDoctorGithub,
53
79
  formatRoutes,
54
80
  inspectApp,
81
+ planDoctorFixes,
55
82
  } from "./inspect.js";
56
83
  export { formatLint, formatLintGithub, lintApp } from "./lint.js";
57
84
  export type { MakeFeatureAddon, MakeFeatureRecipe } from "./make.js";
@@ -18,7 +18,7 @@ import {
18
18
  import {
19
19
  currentGeneratedPackageScripts,
20
20
  externalVersions,
21
- legacyGeneratedPackageScripts,
21
+ isLegacyGeneratedPackageScript,
22
22
  } from "../templates/shared.js";
23
23
 
24
24
  export type MakeResult = {
@@ -1096,7 +1096,7 @@ export async function updatePackageScripts(
1096
1096
  for (const [name, command] of Object.entries(scripts)) {
1097
1097
  if (
1098
1098
  !packageJson.scripts[name] ||
1099
- packageJson.scripts[name] === legacyGeneratedPackageScripts[name]
1099
+ isLegacyGeneratedPackageScript(name, packageJson.scripts[name])
1100
1100
  ) {
1101
1101
  packageJson.scripts[name] = command;
1102
1102
  }
@@ -1162,7 +1162,7 @@ function updateGeneratedPackageScripts(scripts: Record<string, string>): void {
1162
1162
  currentGeneratedPackageScripts,
1163
1163
  )) {
1164
1164
  if (
1165
- scripts[name] === legacyGeneratedPackageScripts[name] ||
1165
+ isLegacyGeneratedPackageScript(name, scripts[name]) ||
1166
1166
  (name === "test" && !scripts[name])
1167
1167
  ) {
1168
1168
  scripts[name] = command;
package/src/mcp.ts CHANGED
@@ -5,9 +5,28 @@ import { z } from "zod";
5
5
  import { mapApp, projectAppMap } from "./app-map.js";
6
6
  import { appMapNodeKinds } from "./app-map-schema.js";
7
7
  import { checkApp } from "./check.js";
8
- import { type ProviderPresetName, providerPresetChoices } from "./choices.js";
8
+ import {
9
+ databaseCommandChoices,
10
+ databaseSchemaDialectChoices,
11
+ databaseSchemaTableChoices,
12
+ doctorFixOperationIds,
13
+ type ProviderPresetName,
14
+ providerPresetChoices,
15
+ } from "./choices.js";
16
+ import {
17
+ defaultDatabaseCommandMaxOutputBytes,
18
+ defaultDatabaseCommandTimeoutMs,
19
+ runDatabaseCommand,
20
+ syncDatabaseSchema,
21
+ } from "./db.js";
9
22
  import { explainApp, explainTargetKinds } from "./explain.js";
10
- import { applyDoctorFixes, inspectApp } from "./inspect.js";
23
+ import {
24
+ applyDoctorFixesWithResult,
25
+ applyDoctorFixPlan,
26
+ createDoctorFixInspectionResult,
27
+ inspectApp,
28
+ planDoctorFixes,
29
+ } from "./inspect.js";
11
30
  import { lintApp } from "./lint.js";
12
31
  import {
13
32
  type MakeResourceResult,
@@ -379,14 +398,14 @@ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
379
398
  "explain",
380
399
  {
381
400
  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.",
401
+ "Explain one mapped Beignet concept or diagnostic using deterministic source evidence. Supports every app-map node kind and returns relationships, relevant conventions, findings, suggested files, and follow-up commands without changing the app.",
383
402
  inputSchema: {
384
403
  kind: z.enum(explainTargetKinds).describe("Mapped concept to explain."),
385
404
  target: z
386
405
  .string()
387
406
  .min(1)
388
407
  .describe(
389
- "Feature name, METHOD /path route, provider package or port name, or diagnostic code.",
408
+ "Stable ID, runtime name, declaration name, source selector, applicable concept alias, or diagnostic code.",
390
409
  ),
391
410
  },
392
411
  annotations: { readOnlyHint: true },
@@ -441,6 +460,107 @@ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
441
460
  ),
442
461
  );
443
462
 
463
+ server.registerTool(
464
+ "db",
465
+ {
466
+ description:
467
+ "Run one app-owned database lifecycle command and return exactly the versioned beignet db <command> --json result. Supports generate, migrate, seed, and reset; captures a bounded output tail, stops after the configured timeout, and cancels the complete child process tree with the MCP request. Dry runs validate prerequisites and report the command without executing it.",
468
+ inputSchema: {
469
+ command: z
470
+ .enum(databaseCommandChoices)
471
+ .describe("Database lifecycle command to run."),
472
+ dryRun: z
473
+ .boolean()
474
+ .optional()
475
+ .describe(
476
+ "Validate prerequisites and report the app-owned script without executing it. This does not simulate SQL or data changes.",
477
+ ),
478
+ timeoutMs: z
479
+ .number()
480
+ .int()
481
+ .min(1_000)
482
+ .max(60 * 60 * 1_000)
483
+ .optional()
484
+ .describe(
485
+ "Maximum command duration in milliseconds. Defaults to 600000 (10 minutes).",
486
+ ),
487
+ },
488
+ annotations: {
489
+ readOnlyHint: false,
490
+ destructiveHint: true,
491
+ idempotentHint: false,
492
+ openWorldHint: true,
493
+ },
494
+ },
495
+ async (input, extra) =>
496
+ safeToolResult(async () =>
497
+ jsonResult(
498
+ await runDatabaseCommand({
499
+ command: input.command,
500
+ cwd,
501
+ captureOutput: true,
502
+ maxOutputBytes: defaultDatabaseCommandMaxOutputBytes,
503
+ signal: extra.signal,
504
+ timeoutMs: input.timeoutMs ?? defaultDatabaseCommandTimeoutMs,
505
+ dryRun: input.dryRun,
506
+ }),
507
+ ),
508
+ ),
509
+ );
510
+
511
+ server.registerTool(
512
+ "db_schema_sync",
513
+ {
514
+ description:
515
+ "Idempotently sync app-owned Drizzle schema re-exports for Beignet provider tables and return exactly the versioned beignet db schema sync --json result. Supports dry-run planning and never generates or applies SQL migrations.",
516
+ inputSchema: {
517
+ dialect: z
518
+ .enum(databaseSchemaDialectChoices)
519
+ .optional()
520
+ .describe(
521
+ "Drizzle dialect. Inferred from registered providers or drizzle.config.* when omitted.",
522
+ ),
523
+ tables: z
524
+ .array(z.enum(databaseSchemaTableChoices))
525
+ .min(1)
526
+ .optional()
527
+ .describe(
528
+ "Beignet provider tables to re-export. Defaults to audit, idempotency, and outbox.",
529
+ ),
530
+ output: z
531
+ .string()
532
+ .min(1)
533
+ .optional()
534
+ .describe(
535
+ "App-relative schema output. Defaults to infra/db/schema/beignet.ts.",
536
+ ),
537
+ dryRun: z
538
+ .boolean()
539
+ .optional()
540
+ .describe("Preview schema source changes without writing files."),
541
+ },
542
+ annotations: {
543
+ readOnlyHint: false,
544
+ destructiveHint: true,
545
+ idempotentHint: true,
546
+ openWorldHint: false,
547
+ },
548
+ },
549
+ async (input, extra) =>
550
+ safeToolResult(async () =>
551
+ jsonResult(
552
+ await syncDatabaseSchema({
553
+ cwd,
554
+ dialect: input.dialect,
555
+ tables: input.tables,
556
+ output: input.output,
557
+ dryRun: input.dryRun,
558
+ signal: extra.signal,
559
+ }),
560
+ ),
561
+ ),
562
+ );
563
+
444
564
  server.registerTool(
445
565
  "routes",
446
566
  {
@@ -469,7 +589,7 @@ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
469
589
  "doctor",
470
590
  {
471
591
  description:
472
- "Check app wiring and Beignet conventions, returning diagnostics, the detected convention, and applied fixes as JSON. Strict mode (the default) is the CI bar and includes CI-oriented warnings. Read-only; use doctor_fix to repair registration drift.",
592
+ "Check app wiring and Beignet conventions, returning diagnostics and the detected convention as JSON. Strict mode (the default) is the CI bar and includes CI-oriented warnings. Read-only; use doctor_fix_plan before a guarded doctor_fix repair.",
473
593
  inputSchema: {
474
594
  strict: z
475
595
  .boolean()
@@ -489,28 +609,82 @@ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
489
609
  }),
490
610
  );
491
611
 
612
+ server.registerTool(
613
+ "doctor_fix_plan",
614
+ {
615
+ description:
616
+ "Plan every currently available low-risk doctor repair without changing files. Returns stable operation IDs, exact unified patches, file hashes, and a plan ID for guarded doctor_fix application.",
617
+ inputSchema: {
618
+ strict: z
619
+ .boolean()
620
+ .optional()
621
+ .describe("Include CI-oriented warnings. Defaults to true."),
622
+ },
623
+ annotations: {
624
+ readOnlyHint: true,
625
+ destructiveHint: false,
626
+ idempotentHint: true,
627
+ openWorldHint: false,
628
+ },
629
+ },
630
+ async (input) =>
631
+ safeToolResult(async () => {
632
+ return jsonResult(
633
+ await planDoctorFixes({ cwd, strict: input.strict ?? true }),
634
+ );
635
+ }),
636
+ );
637
+
492
638
  server.registerTool(
493
639
  "doctor_fix",
494
640
  {
495
641
  description:
496
- "Apply low-risk doctor fixes, then re-check the app. Repairs route-group, schedule, task, outbox, and listener registration drift. Returns applied fixes and remaining diagnostics as JSON.",
642
+ "Apply low-risk doctor repairs, then re-check the app. With no planId, preserves the legacy apply-all behavior. Pass the planId returned by doctor_fix_plan for stale-file protection, optionally with fixIds to select repair operations.",
497
643
  inputSchema: {
498
644
  strict: z
499
645
  .boolean()
500
646
  .optional()
501
647
  .describe("Include CI-oriented warnings. Defaults to true."),
648
+ planId: z
649
+ .string()
650
+ .min(1)
651
+ .optional()
652
+ .describe("Plan ID returned by doctor_fix_plan."),
653
+ fixIds: z
654
+ .array(z.enum(doctorFixOperationIds))
655
+ .min(1)
656
+ .optional()
657
+ .describe("Repair operation IDs to apply. Requires planId."),
658
+ },
659
+ annotations: {
660
+ readOnlyHint: false,
661
+ destructiveHint: true,
662
+ idempotentHint: true,
663
+ openWorldHint: false,
502
664
  },
503
665
  },
504
666
  async (input) =>
505
667
  safeToolResult(async () => {
506
668
  const strict = input.strict ?? true;
507
- const fixes = await applyDoctorFixes({ cwd, strict });
508
- const result = await inspectApp({ cwd, strict });
509
- return jsonResult({
510
- diagnostics: result.diagnostics,
511
- convention: result.convention,
512
- fixes,
513
- });
669
+ if (input.fixIds && !input.planId) {
670
+ throw new Error(
671
+ "doctor_fix fixIds require planId from doctor_fix_plan.",
672
+ );
673
+ }
674
+ const applied = input.planId
675
+ ? await applyDoctorFixPlan({
676
+ cwd,
677
+ strict,
678
+ planId: input.planId,
679
+ ...(input.fixIds ? { fixIds: input.fixIds } : {}),
680
+ })
681
+ : await applyDoctorFixesWithResult({ cwd, strict });
682
+ return jsonResult(
683
+ createDoctorFixInspectionResult(
684
+ await inspectApp({ cwd, strict }),
685
+ applied,
686
+ ),
687
+ );
514
688
  }),
515
689
  );
516
690
 
@@ -62,8 +62,11 @@ use, so their absence is fine. \`${cli} make event\`, \`${cli} make job\`,
62
62
  \`${cli} make listener\`, \`${cli} make schedule\`, \`${cli} make task\`,
63
63
  \`${cli} make seed\`, and \`${cli} make upload\` create or update their
64
64
  required app entrypoints.
65
- \`${cli} doctor\` detects registration drift. \`${cli} doctor --fix\` repairs
66
- route-group, schedule, task, outbox, and listener registration.
65
+ \`${cli} doctor\` detects registration drift. Preview exact repair patches
66
+ with \`${cli} doctor --fix --dry-run\`, then apply the returned plan with
67
+ \`${cli} doctor --fix --plan <plan-id>\` and optional
68
+ \`--only <operation-ids>\`. Plain \`${cli} doctor --fix\` remains the
69
+ apply-all shortcut.
67
70
 
68
71
  ## Prefer generators
69
72
 
@@ -73,6 +76,9 @@ artifact list. Use \`${cli} make feature <name> --recipe full-slice\` when you
73
76
  need a richer reference slice with policy, client helpers, workflow artifacts,
74
77
  events, listener registration, jobs, and outbox wiring. After changing the Drizzle schema in
75
78
  \`infra/db/schema/\`, run \`${cli} db generate\` then \`${cli} db migrate\`.
79
+ When MCP is available, use \`db_schema_sync\` for Beignet provider table
80
+ re-exports and \`db\` for \`generate\`, \`migrate\`, \`seed\`, or \`reset\`
81
+ instead of falling back to a shell.
76
82
 
77
83
  ## The framework already solves these
78
84
 
@@ -148,17 +154,27 @@ skill-loading block.
148
154
  \`.mcp.json\` registers the app-local \`@beignet/cli\` bin at
149
155
  \`./node_modules/.bin/beignet mcp\`, which exposes the app map, validation, and
150
156
  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
157
+ \`check\`, \`db\`, \`db_schema_sync\`, \`routes\`, \`doctor\`,
158
+ \`doctor_fix_plan\`, \`doctor_fix\`, \`lint\`, \`make\`, \`provider_add\`.
159
+ Use \`doctor_fix_plan\` to inspect
160
+ stable operation IDs, hashes, exact patches, and current diagnostics before
161
+ passing its \`planId\` and optional \`fixIds\` to \`doctor_fix\`. Guarded
162
+ apply returns the same plan metadata and inspection payload as the CLI. Use
163
+ \`app_map\` before broad code search and pass a feature
153
164
  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
165
+ relationships, conventions, findings, and suggested files for any mapped
166
+ concept or diagnostic; its kinds match the app map. Use \`check\` after edits
167
+ to run the complete validation loop; it returns the same versioned result as
157
168
  \`${cli} check --json\`, bounds failure output, and cancels the active package
158
169
  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.
170
+ app-owned scripts retain their normal side effects. Use \`db_schema_sync\` to
171
+ preview or apply idempotent provider-table schema re-exports, then call \`db\`
172
+ with \`generate\` and \`migrate\`; database output is bounded, commands time
173
+ out, and cancellation stops the active process tree. Treat \`seed\` and
174
+ especially \`reset\` as app-owned mutations. Lifecycle \`dryRun\` validates
175
+ and reports the script without executing it; it does not simulate SQL or data
176
+ changes. Clients that do not read \`.mcp.json\` can use the same command from
177
+ the app root; use \`${cli} mcp\` only for terminal debugging.
162
178
  `;
163
179
  }
164
180
 
@@ -1,6 +1,7 @@
1
1
  import { databaseLocalUrl, databaseStartCommand } from "../choices.js";
2
2
  import { shadcnDependencies, shadcnDevDependencies } from "./shadcn.js";
3
3
  import {
4
+ currentGeneratedPackageScripts,
4
5
  externalVersions,
5
6
  hasStarterProvider,
6
7
  json,
@@ -97,11 +98,11 @@ export function packageJson(ctx: TemplateContext): string {
97
98
  start: "next start",
98
99
  lint: "biome lint .",
99
100
  format: "biome format --write .",
100
- test: "tsx lib/beignet-test-runner.ts",
101
+ test: currentGeneratedPackageScripts.test,
101
102
  typecheck: "tsc --noEmit",
102
103
  "db:generate": "drizzle-kit generate",
103
104
  "db:migrate": "drizzle-kit migrate",
104
- "db:reset": "tsx infra/db/reset.ts",
105
+ "db:reset": currentGeneratedPackageScripts["db:reset"],
105
106
  };
106
107
 
107
108
  return json({
@@ -67,18 +67,26 @@ export const externalVersions = {
67
67
  upstashRedis: "^1.0.0",
68
68
  };
69
69
 
70
- export const legacyGeneratedPackageScripts: Record<string, string> = {
71
- "db:reset": "bun infra/db/reset.ts",
72
- "db:seed": "bun server/seed.ts",
73
- test: "bun test",
74
- };
70
+ export const legacyGeneratedPackageScripts: Record<string, readonly string[]> =
71
+ {
72
+ "db:reset": ["bun infra/db/reset.ts"],
73
+ "db:seed": ["bun server/seed.ts"],
74
+ test: ["bun test", "tsx lib/beignet-test-runner.ts"],
75
+ };
75
76
 
76
77
  export const currentGeneratedPackageScripts: Record<string, string> = {
77
78
  "db:reset": "tsx infra/db/reset.ts",
78
79
  "db:seed": "tsx server/seed.ts",
79
- test: "tsx lib/beignet-test-runner.ts",
80
+ test: "node --import tsx lib/beignet-test-runner.ts",
80
81
  };
81
82
 
83
+ export function isLegacyGeneratedPackageScript(
84
+ name: string,
85
+ command: string | undefined,
86
+ ): boolean {
87
+ return legacyGeneratedPackageScripts[name]?.includes(command ?? "") ?? false;
88
+ }
89
+
82
90
  /**
83
91
  * Dialect-specific names used when rendering database-aware templates.
84
92
  */