@beignet/cli 0.0.49 → 0.0.51

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 (64) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +54 -5
  3. package/dist/analysis/workspace.d.ts +1 -0
  4. package/dist/analysis/workspace.d.ts.map +1 -1
  5. package/dist/analysis/workspace.js +20 -10
  6. package/dist/analysis/workspace.js.map +1 -1
  7. package/dist/app-map-changes.d.ts +103 -0
  8. package/dist/app-map-changes.d.ts.map +1 -0
  9. package/dist/app-map-changes.js +949 -0
  10. package/dist/app-map-changes.js.map +1 -0
  11. package/dist/git-changes.d.ts +30 -0
  12. package/dist/git-changes.d.ts.map +1 -0
  13. package/dist/git-changes.js +367 -0
  14. package/dist/git-changes.js.map +1 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +58 -4
  18. package/dist/index.js.map +1 -1
  19. package/dist/inspect.js +413 -43
  20. package/dist/inspect.js.map +1 -1
  21. package/dist/lib.d.ts +3 -0
  22. package/dist/lib.d.ts.map +1 -1
  23. package/dist/lib.js +1 -0
  24. package/dist/lib.js.map +1 -1
  25. package/dist/make/shared.d.ts.map +1 -1
  26. package/dist/make/shared.js +31 -27
  27. package/dist/make/shared.js.map +1 -1
  28. package/dist/make.d.ts.map +1 -1
  29. package/dist/make.js +0 -2
  30. package/dist/make.js.map +1 -1
  31. package/dist/mcp.d.ts.map +1 -1
  32. package/dist/mcp.js +46 -6
  33. package/dist/mcp.js.map +1 -1
  34. package/dist/operational-process.d.ts +1 -0
  35. package/dist/operational-process.d.ts.map +1 -1
  36. package/dist/operational-process.js.map +1 -1
  37. package/dist/operational-runner.js +1 -0
  38. package/dist/operational-runner.js.map +1 -1
  39. package/dist/outbox.d.ts +1 -0
  40. package/dist/outbox.d.ts.map +1 -1
  41. package/dist/outbox.js +58 -12
  42. package/dist/outbox.js.map +1 -1
  43. package/dist/templates/agents.d.ts.map +1 -1
  44. package/dist/templates/agents.js +28 -3
  45. package/dist/templates/agents.js.map +1 -1
  46. package/dist/templates/base.d.ts.map +1 -1
  47. package/dist/templates/base.js +4 -1
  48. package/dist/templates/base.js.map +1 -1
  49. package/package.json +2 -2
  50. package/skills/app-structure/SKILL.md +30 -5
  51. package/src/analysis/workspace.ts +25 -9
  52. package/src/app-map-changes.ts +1462 -0
  53. package/src/git-changes.ts +511 -0
  54. package/src/index.ts +84 -4
  55. package/src/inspect.ts +586 -51
  56. package/src/lib.ts +21 -0
  57. package/src/make/shared.ts +31 -27
  58. package/src/make.ts +0 -2
  59. package/src/mcp.ts +65 -12
  60. package/src/operational-process.ts +1 -0
  61. package/src/operational-runner.ts +1 -0
  62. package/src/outbox.ts +63 -12
  63. package/src/templates/agents.ts +28 -3
  64. package/src/templates/base.ts +4 -1
package/src/lib.ts CHANGED
@@ -25,6 +25,23 @@ export {
25
25
  mapApp,
26
26
  projectAppMap,
27
27
  } from "./app-map.js";
28
+ export type {
29
+ AppChangedFile,
30
+ AppChangeFileScope,
31
+ AppChangeGap,
32
+ AppChangeImpact,
33
+ AppChangeImpactedNode,
34
+ AppChangeImpactResult,
35
+ AppChangeReason,
36
+ AppChangeRelationship,
37
+ AppMapChangesOptions,
38
+ BoundedAppChangeGapSection,
39
+ BoundedAppChangeSection,
40
+ } from "./app-map-changes.js";
41
+ export {
42
+ formatAppChangeImpact,
43
+ mapAppChanges,
44
+ } from "./app-map-changes.js";
28
45
  export type { CreateOptions } from "./create.js";
29
46
  export { createProject } from "./create.js";
30
47
  export type {
@@ -71,6 +88,10 @@ export {
71
88
  explainTargetKinds,
72
89
  formatExplain,
73
90
  } from "./explain.js";
91
+ export type {
92
+ GitChangeComparison,
93
+ GitChangedFileStatus,
94
+ } from "./git-changes.js";
74
95
  export { main } from "./index.js";
75
96
  export type {
76
97
  ApplyDoctorFixPlanOptions,
@@ -453,36 +453,40 @@ export function wireListenersProviderSource(
453
453
  >()({
454
454
  \tname: "app-listeners",
455
455
  \tsetup({ ports, createServiceContext }) {
456
- \t\tconst unregister = registerListeners(ports.eventBus, listeners, {
457
- \t\t\tctx: () =>
458
- \t\t\t\tcreateServiceContext({
459
- \t\t\t\t\tactor: createServiceActor("beignet-listener"),
460
- \t\t\t\t}),
461
- \t\t\tonError(error, listener) {
462
- \t\t\t\tports.logger.error("Event listener failed", {
463
- \t\t\t\t\terror,
464
- \t\t\t\t\tlistenerName: listener.name,
465
- \t\t\t\t});
466
- \t\t\t\tvoid tryReportException({
467
- \t\t\t\t\treporter: ports.errorReporter,
468
- \t\t\t\t\terror,
469
- \t\t\t\t\treportOptions: {
470
- \t\t\t\t\t\tlevel: "error",
471
- \t\t\t\t\t\tmechanism: "beignet.listener",
472
- \t\t\t\t\t\thandled: false,
473
- \t\t\t\t\t\ttags: {
474
- \t\t\t\t\t\t\t"beignet.kind": "listener",
475
- \t\t\t\t\t\t\t"beignet.listener": listener.name,
476
- \t\t\t\t\t\t},
477
- \t\t\t\t\t\tcontexts: { listener: { name: listener.name } },
456
+ \t\tlet registration: ReturnType<typeof registerListeners> | undefined;
457
+
458
+ \t\treturn {
459
+ \t\t\tasync start() {
460
+ \t\t\t\tregistration = registerListeners(ports.eventBus, listeners, {
461
+ \t\t\t\t\tctx: () =>
462
+ \t\t\t\t\t\tcreateServiceContext({
463
+ \t\t\t\t\t\t\tactor: createServiceActor("beignet-listener"),
464
+ \t\t\t\t\t\t}),
465
+ \t\t\t\t\tonError(error, listener) {
466
+ \t\t\t\t\t\tports.logger.error("Event listener failed", {
467
+ \t\t\t\t\t\t\terror,
468
+ \t\t\t\t\t\t\tlistenerName: listener.name,
469
+ \t\t\t\t\t\t});
470
+ \t\t\t\t\t\tvoid tryReportException({
471
+ \t\t\t\t\t\t\treporter: ports.errorReporter,
472
+ \t\t\t\t\t\t\terror,
473
+ \t\t\t\t\t\t\treportOptions: {
474
+ \t\t\t\t\t\t\t\tlevel: "error",
475
+ \t\t\t\t\t\t\t\tmechanism: "beignet.listener",
476
+ \t\t\t\t\t\t\t\thandled: false,
477
+ \t\t\t\t\t\t\t\ttags: {
478
+ \t\t\t\t\t\t\t\t\t"beignet.kind": "listener",
479
+ \t\t\t\t\t\t\t\t\t"beignet.listener": listener.name,
480
+ \t\t\t\t\t\t\t\t},
481
+ \t\t\t\t\t\t\t\tcontexts: { listener: { name: listener.name } },
482
+ \t\t\t\t\t\t\t},
483
+ \t\t\t\t\t\t});
478
484
  \t\t\t\t\t},
479
485
  \t\t\t\t});
486
+ \t\t\t\tawait registration.ready;
480
487
  \t\t\t},
481
- \t\t});
482
-
483
- \t\treturn {
484
- \t\t\tstop() {
485
- \t\t\t\tunregister();
488
+ \t\t\tasync stop() {
489
+ \t\t\t\tawait registration?.unsubscribe();
486
490
  \t\t\t},
487
491
  \t\t};
488
492
  \t},
package/src/make.ts CHANGED
@@ -2225,7 +2225,6 @@ async function updateOutboxDrainProvider(
2225
2225
  \t\t\tdefer: after,
2226
2226
  \t\t\tcreateContext: () => createServiceContext(undefined),
2227
2227
  \t\t\tregistry: async () => (await import("${outboxModule}")).outboxRegistry,
2228
- \t\t\tbatchSize: 100,
2229
2228
  \t\t});
2230
2229
 
2231
2230
  \t\treturn {
@@ -5921,7 +5920,6 @@ export const { GET, POST } = createOutboxDrainRoute({
5921
5920
  \tserver: getServer,
5922
5921
  \tregistry: outboxRegistry,
5923
5922
  \tsecret: env.CRON_SECRET,
5924
- \tbatchSize: 100,
5925
5923
  });
5926
5924
  `;
5927
5925
  }
package/src/mcp.ts CHANGED
@@ -4,6 +4,7 @@ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/server";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
5
5
  import { z } from "zod/v4";
6
6
  import { mapApp, projectAppMap } from "./app-map.js";
7
+ import { mapAppChanges } from "./app-map-changes.js";
7
8
  import { appMapNodeKinds } from "./app-map-schema.js";
8
9
  import { checkApp } from "./check.js";
9
10
  import {
@@ -284,6 +285,7 @@ const outboxRunInputSchema = z.discriminatedUnion("operation", [
284
285
  z.object({
285
286
  operation: z.literal("drain"),
286
287
  batchSize: z.number().int().positive().optional(),
288
+ concurrency: z.number().int().positive().optional(),
287
289
  module: operationalModuleSchema,
288
290
  timeoutMs: operationalTimeoutSchema,
289
291
  }),
@@ -348,17 +350,36 @@ function jsonResult(value: unknown): McpToolResult {
348
350
  };
349
351
  }
350
352
 
351
- function operationalJsonResult(value: unknown): McpToolResult {
353
+ function operationalJsonResult(
354
+ value: unknown,
355
+ options: { isError?: boolean } = {},
356
+ ): McpToolResult {
352
357
  const formatted = JSON.stringify(value, null, 2);
353
358
  const text =
354
359
  Buffer.byteLength(formatted) <= defaultOperationalResultMaxBytes
355
360
  ? formatted
356
361
  : JSON.stringify(value);
357
362
  return {
363
+ ...(options.isError ? { isError: true } : {}),
358
364
  content: [{ type: "text", text }],
359
365
  };
360
366
  }
361
367
 
368
+ function isUncertainOutboxDrainReport(value: unknown): boolean {
369
+ if (typeof value !== "object" || value === null || !("result" in value)) {
370
+ return false;
371
+ }
372
+ const result = value.result;
373
+ if (typeof result !== "object" || result === null) return false;
374
+ const settlementFailed =
375
+ "settlementFailed" in result ? result.settlementFailed : undefined;
376
+ const leaseLost = "leaseLost" in result ? result.leaseLost : undefined;
377
+ return (
378
+ (typeof settlementFailed === "number" && settlementFailed > 0) ||
379
+ (typeof leaseLost === "number" && leaseLost > 0)
380
+ );
381
+ }
382
+
362
383
  function errorResult(error: unknown): McpToolResult {
363
384
  return {
364
385
  isError: true,
@@ -579,8 +600,21 @@ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
579
600
  "app_map",
580
601
  {
581
602
  description:
582
- "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.",
603
+ "Return a deterministic graph of this Beignet app, or map the current Git change set to bounded, source-backed concepts with changed=true. Changed mode is report-only and exposes unresolved evidence without claiming verification.",
583
604
  inputSchema: {
605
+ changed: z
606
+ .boolean()
607
+ .optional()
608
+ .describe(
609
+ "Map the current Git change set instead of returning the complete app graph.",
610
+ ),
611
+ base: z
612
+ .string()
613
+ .min(1)
614
+ .optional()
615
+ .describe(
616
+ "Include committed changes since the merge base of this already-local Git ref. Requires changed=true; Beignet never fetches it.",
617
+ ),
584
618
  feature: z
585
619
  .string()
586
620
  .optional()
@@ -598,6 +632,22 @@ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
598
632
  },
599
633
  async (input) =>
600
634
  safeToolResult(async () => {
635
+ if (input.base !== undefined && !input.changed) {
636
+ throw new Error("app_map base requires changed=true.");
637
+ }
638
+ if (
639
+ input.changed &&
640
+ (input.feature !== undefined ||
641
+ input.kinds !== undefined ||
642
+ input.includeDiagnostics !== undefined)
643
+ ) {
644
+ throw new Error(
645
+ "app_map changed=true cannot be combined with feature, kinds, or includeDiagnostics.",
646
+ );
647
+ }
648
+ if (input.changed) {
649
+ return jsonResult(await mapAppChanges({ cwd, base: input.base }));
650
+ }
601
651
  const result = await mapApp({ cwd, strict: true });
602
652
  return jsonResult(
603
653
  projectAppMap(result, {
@@ -942,7 +992,7 @@ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
942
992
  "outbox_run",
943
993
  {
944
994
  description:
945
- "Run a state-changing outbox drain, requeue, purge, or prune operation and return exactly the matching versioned beignet outbox <operation> --json report. Purge and prune support dryRun. Runs in an isolated process with bounded structured output, cancellation, and a timeout.",
995
+ "Run a state-changing outbox drain, requeue, purge, or prune operation and return exactly the matching versioned beignet outbox <operation> --json report. Drain supports bounded unordered concurrency and returns an error-bearing report when settlement or lease state is uncertain. Purge and prune support dryRun. Runs in an isolated process with bounded structured output, cancellation, and a timeout.",
946
996
  inputSchema: outboxRunInputSchema,
947
997
  annotations: {
948
998
  readOnlyHint: false,
@@ -958,6 +1008,7 @@ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
958
1008
  ? {
959
1009
  command: "outbox:drain" as const,
960
1010
  batchSize: input.batchSize,
1011
+ concurrency: input.concurrency,
961
1012
  modulePath: input.module,
962
1013
  }
963
1014
  : input.operation === "requeue"
@@ -984,14 +1035,16 @@ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
984
1035
  dryRun: input.dryRun,
985
1036
  modulePath: input.module,
986
1037
  };
987
- return operationalJsonResult(
988
- await runIsolatedOperationalCommand({
989
- cwd,
990
- request,
991
- signal: ctx.mcpReq.signal,
992
- timeoutMs: input.timeoutMs ?? defaultOperationalCommandTimeoutMs,
993
- }),
994
- );
1038
+ const result = await runIsolatedOperationalCommand({
1039
+ cwd,
1040
+ request,
1041
+ signal: ctx.mcpReq.signal,
1042
+ timeoutMs: input.timeoutMs ?? defaultOperationalCommandTimeoutMs,
1043
+ });
1044
+ return operationalJsonResult(result, {
1045
+ isError:
1046
+ input.operation === "drain" && isUncertainOutboxDrainReport(result),
1047
+ });
995
1048
  }),
996
1049
  );
997
1050
 
@@ -1166,7 +1219,7 @@ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
1166
1219
  async function readAppGuidance(cwd: string): Promise<string> {
1167
1220
  const guidancePath = await resolveAppGuidancePath(cwd);
1168
1221
  if (!guidancePath) {
1169
- return "# Beignet app guidance\n\nNo app-local AGENTS.md was found. Use the app_map and explain tools before editing, prefer Beignet generators, and run check after changes.\n";
1222
+ return "# Beignet app guidance\n\nNo app-local AGENTS.md was found. Use app_map and explain before editing, prefer Beignet generators, use app_map with changed=true to inspect potential consumers after editing in Git, and then run check.\n";
1170
1223
  }
1171
1224
 
1172
1225
  let file: Awaited<ReturnType<typeof open>>;
@@ -44,6 +44,7 @@ export type OperationalCommandRequest =
44
44
  | {
45
45
  command: "outbox:drain";
46
46
  batchSize?: number;
47
+ concurrency?: number;
47
48
  modulePath?: string;
48
49
  }
49
50
  | {
@@ -47,6 +47,7 @@ async function runOperationalRequest(input: OperationalRunnerInput) {
47
47
  return runOutboxDrain({
48
48
  cwd,
49
49
  batchSize: request.batchSize,
50
+ concurrency: request.concurrency,
50
51
  modulePath: request.modulePath,
51
52
  });
52
53
  case "outbox:list":
package/src/outbox.ts CHANGED
@@ -10,6 +10,7 @@ import type {
10
10
  OutboxMessageStatus,
11
11
  OutboxRegistry,
12
12
  } from "@beignet/core/outbox";
13
+ import type { TracingPort } from "@beignet/core/tracing";
13
14
  import { createJiti } from "jiti";
14
15
  import { loadBeignetConfig, normalizePath } from "./config.js";
15
16
  import {
@@ -26,6 +27,7 @@ export type RunOutboxDrainOptions = {
26
27
  cwd?: string;
27
28
  modulePath?: string;
28
29
  batchSize?: number;
30
+ concurrency?: number;
29
31
  };
30
32
 
31
33
  /**
@@ -175,6 +177,7 @@ type OutboxDrainPorts = {
175
177
  logger?: OutboxDrainLogger;
176
178
  devtools?: OutboxDrainInstrumentation;
177
179
  instrumentation?: OutboxDrainInstrumentation;
180
+ tracing?: TracingPort;
178
181
  errorReporter?: ErrorReporterPort;
179
182
  };
180
183
 
@@ -187,6 +190,7 @@ type OutboxDrainContext = {
187
190
  type OutboxDrainContextArgs = {
188
191
  registry: OutboxRegistry;
189
192
  batchSize?: number;
193
+ concurrency?: number;
190
194
  };
191
195
 
192
196
  type OutboxAdminOperation = "list" | "show" | "requeue" | "purge" | "prune";
@@ -219,7 +223,11 @@ export async function runOutboxDrain(
219
223
  const config = await loadBeignetConfig(cwd);
220
224
  const modulePath = normalizePath(options.modulePath ?? config.paths.outbox);
221
225
  const batchSize = options.batchSize;
226
+ const concurrency = options.concurrency;
222
227
  if (batchSize !== undefined) assertPositiveInteger("batchSize", batchSize);
228
+ if (concurrency !== undefined) {
229
+ assertPositiveInteger("concurrency", concurrency);
230
+ }
223
231
 
224
232
  const startedAt = performance.now();
225
233
  const outboxModule = await loadOutboxModule(cwd, modulePath);
@@ -227,7 +235,7 @@ export async function runOutboxDrain(
227
235
  outboxModule.outboxRegistry,
228
236
  modulePath,
229
237
  );
230
- const contextArgs = { registry, batchSize };
238
+ const contextArgs = { registry, batchSize, concurrency };
231
239
  const rawContext = outboxModule.createOutboxDrainContext
232
240
  ? await outboxModule.createOutboxDrainContext(contextArgs)
233
241
  : undefined;
@@ -247,7 +255,11 @@ export async function runOutboxDrain(
247
255
  eventBus: ctx.ports.eventBus,
248
256
  jobs: ctx.ports.jobs,
249
257
  batchSize,
250
- instrumentation: ctx.ports.instrumentation ?? ctx.ports.devtools,
258
+ concurrency,
259
+ instrumentation: {
260
+ instrumentation: ctx.ports.instrumentation ?? ctx.ports.devtools,
261
+ tracing: ctx.ports.tracing,
262
+ },
251
263
  instrumentationContext: {
252
264
  requestId: ctx.requestId,
253
265
  traceId: ctx.traceId,
@@ -288,27 +300,65 @@ export async function runOutboxDrain(
288
300
  },
289
301
  });
290
302
  },
291
- onSettlementError(settlementError, message) {
303
+ onLeaseError(failure) {
304
+ const outcome =
305
+ failure.state === "lost"
306
+ ? "leaseLost"
307
+ : failure.state === "recovered"
308
+ ? "leaseRecovered"
309
+ : "leaseDegraded";
292
310
  return reportOperationalFailure({
293
311
  ctx,
294
- error: settlementError,
312
+ error: failure.error,
313
+ reportOptions: {
314
+ level: failure.state === "lost" ? "error" : "warning",
315
+ mechanism: "beignet.outbox.cli",
316
+ handled: failure.state !== "lost",
317
+ tags: {
318
+ "beignet.kind": "outbox",
319
+ "beignet.outbox.kind": failure.message.kind,
320
+ "beignet.outbox.name": failure.message.name,
321
+ "beignet.outbox.outcome": outcome,
322
+ },
323
+ contexts: {
324
+ outbox: {
325
+ messageId: failure.message.id,
326
+ kind: failure.message.kind,
327
+ name: failure.message.name,
328
+ attempts: failure.message.attempts,
329
+ maxAttempts: failure.message.maxAttempts,
330
+ operation: failure.operation,
331
+ state: failure.state,
332
+ confirmedLost: failure.confirmedLost,
333
+ outcome,
334
+ },
335
+ },
336
+ },
337
+ });
338
+ },
339
+ onSettlementError(failure) {
340
+ return reportOperationalFailure({
341
+ ctx,
342
+ error: failure.error,
295
343
  reportOptions: {
296
344
  level: "error",
297
345
  mechanism: "beignet.outbox.cli",
298
346
  handled: false,
299
347
  tags: {
300
348
  "beignet.kind": "outbox",
301
- "beignet.outbox.kind": message.kind,
302
- "beignet.outbox.name": message.name,
349
+ "beignet.outbox.kind": failure.message.kind,
350
+ "beignet.outbox.name": failure.message.name,
303
351
  "beignet.outbox.outcome": "settlementFailed",
304
352
  },
305
353
  contexts: {
306
354
  outbox: {
307
- messageId: message.id,
308
- kind: message.kind,
309
- name: message.name,
310
- attempts: message.attempts,
311
- maxAttempts: message.maxAttempts,
355
+ messageId: failure.message.id,
356
+ kind: failure.message.kind,
357
+ name: failure.message.name,
358
+ attempts: failure.message.attempts,
359
+ maxAttempts: failure.message.maxAttempts,
360
+ operation: failure.operation,
361
+ deliverySucceeded: failure.deliverySucceeded,
312
362
  outcome: "settlementFailed",
313
363
  },
314
364
  },
@@ -620,6 +670,7 @@ function isOutboxPort(value: unknown): value is DrainOutboxOptions["outbox"] {
620
670
  isRecord(value) &&
621
671
  typeof value.enqueue === "function" &&
622
672
  typeof value.claimBatch === "function" &&
673
+ typeof value.renewClaim === "function" &&
623
674
  typeof value.markDelivered === "function" &&
624
675
  typeof value.markFailed === "function"
625
676
  );
@@ -677,7 +728,7 @@ async function recordOutboxDrain(
677
728
  watcher: "outbox",
678
729
  name: "outbox.drain",
679
730
  label: "Outbox drain",
680
- summary: `${result.delivered} delivered, ${result.retried} retried, ${result.deadLettered} dead-lettered`,
731
+ summary: `${result.delivered} delivered, ${result.retried} retried, ${result.deadLettered} dead-lettered, ${result.settlementFailed} settlement-uncertain, ${result.leaseLost} lease-lost`,
681
732
  requestId: ctx.requestId,
682
733
  traceId: ctx.traceId,
683
734
  details: result,
@@ -64,6 +64,8 @@ is a silent failure — the file exists but never runs:
64
64
  fallback and rejects unmarked custom inline wiring as a conflict.
65
65
  - Listeners must be added to the \`listeners\` array in \`server/listeners.ts\`
66
66
  and wired through a \`registerListeners(...)\` call in server provider wiring.
67
+ Register them in \`start()\`, await \`registration.ready\`, and await
68
+ \`registration.unsubscribe()\` in \`stop()\`.
67
69
  - Apps using Inngest must also spread feature job registries into
68
70
  \`inngestJobs\` in \`server/inngest.ts\`.
69
71
  - Apps using runtime integrity must list workflow registries in
@@ -105,7 +107,9 @@ re-exports and \`db\` for \`generate\`, \`migrate\`, \`seed\`, or \`reset\`
105
107
  instead of falling back to a shell. Use the read-only \`db_status\` tool before
106
108
  deploying. Use \`task_run\`, \`schedule_run\`,
107
109
  \`outbox_inspect\`, and \`outbox_run\` for registered operational workflows
108
- and outbox recovery.
110
+ and outbox recovery. Treat a drain with nonzero \`settlementFailed\` or
111
+ \`leaseLost\` as an operational failure. Parallel delivery is opt-in and does
112
+ not preserve message order.
109
113
 
110
114
  ## The framework already solves these
111
115
 
@@ -157,6 +161,15 @@ to one feature. The graph honors the app's complete TypeScript \`baseUrl\` and
157
161
  edges as proof that no relationship exists. Registration diagnostics expose
158
162
  the declaration as \`subject.file\` and \`subject.exportName\`.
159
163
 
164
+ After editing in a Git worktree, run \`${cli} map --changed\` before the full
165
+ check. It reports direct changes, reverse imports, one-hop semantic consumers,
166
+ feature context, and unresolved evidence without claiming that the change is
167
+ correct. Use \`--base <local-ref>\` for branch-wide impact from the merge base;
168
+ Beignet never fetches the ref. Recognized guide files and
169
+ root documentation-directory content stay non-impacting; other \`.mdx\` and
170
+ \`.txt\` paths remain application changes. Changed files with no mapped concept
171
+ appear as explicit gaps.
172
+
160
173
  ## Package skills
161
174
 
162
175
  This app trusts Beignet's package-shipped agent skills through
@@ -173,6 +186,12 @@ skill-loading block.
173
186
 
174
187
  ## Placement rules
175
188
 
189
+ - Checked-in SQL migrations and Drizzle metadata live in root \`drizzle/\`.
190
+ - Seed generators compose feature seeds through the optional
191
+ \`server/seed.ts\` entrypoint.
192
+ - Long-running runtime entrypoints such as BullMQ workers live under optional
193
+ \`server/workers/\`; never start worker loops in lifecycle providers or
194
+ serverless route modules.
176
195
  - Feature artifacts live under \`features/<feature>/\`, with tests in
177
196
  \`features/<feature>/tests/\` (not \`__tests__/\`).
178
197
  - Feature-specific client data-fetching helpers, query options, mutation
@@ -204,7 +223,10 @@ stable operation IDs, hashes, exact patches, and current diagnostics before
204
223
  passing its \`planId\` and optional \`fixIds\` to \`doctor_fix\`. Guarded
205
224
  apply returns the same plan metadata and inspection payload as the CLI. Use
206
225
  \`app_map\` before broad code search and pass a feature
207
- or node kinds to keep context focused. Use \`explain\` for source-backed
226
+ or node kinds to keep context focused. After edits, pass
227
+ \`{ changed: true, base?: string }\` for the same report as
228
+ \`${cli} map --changed --json\`; changed mode rejects projection and diagnostic
229
+ inputs. Use \`explain\` for source-backed
208
230
  relationships, conventions, findings, and suggested files for any mapped
209
231
  concept or diagnostic; its kinds match the app map. Use \`check\` after edits
210
232
  to run the complete validation loop; it returns the same versioned result as
@@ -222,7 +244,10 @@ and reports the script without executing it; it does not simulate SQL or data
222
244
  changes. Use \`task_run\` and \`schedule_run\` for registered operational
223
245
  workflows, \`outbox_inspect\` for read-only \`list\` and \`show\`, and
224
246
  \`outbox_run\` for \`drain\`, \`requeue\`, \`purge\`, or \`prune\`; purge and
225
- prune support \`dryRun\`. Operational commands run in isolated process trees
247
+ prune support \`dryRun\`. A drain with nonzero \`settlementFailed\` or
248
+ \`leaseLost\` remains a complete JSON report but is an error result; inspect
249
+ storage health and clock synchronization before retrying. Operational commands
250
+ run in isolated process trees
226
251
  with bounded results, cancellation, and timeouts. Optional \`module\` overrides
227
252
  must remain inside the app root where the MCP server started. Cancellation and
228
253
  timeouts cannot roll back side effects that already completed. Inspect app
@@ -324,7 +324,10 @@ Use \`${cli} make feature projects --recipe full-slice\` when you want a richer
324
324
 
325
325
  Run \`${cli} map\` for the current architecture or \`${cli} map --json\` for
326
326
  the versioned graph used by coding agents. Add \`--feature todos\` to focus on
327
- one slice and its direct relationships.
327
+ one slice and its direct relationships. After editing in a Git worktree, run
328
+ \`${cli} map --changed\` to inspect direct changes, potential semantic
329
+ consumers, feature context, and unresolved evidence before \`${cli} check\`.
330
+ The changed map is report-only and does not prove correctness.
328
331
 
329
332
  - \`features/todos/contracts.ts\` owns the HTTP contract and reuses shared feature schemas.
330
333
  - \`features/todos/schemas.ts\` owns shared DTO and validation schemas used by contracts, use cases, ports, tests, and clients.