@pattern-stack/codegen 0.11.0 → 0.12.1

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 (37) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/dist/runtime/subsystems/index.d.ts +7 -3
  3. package/dist/runtime/subsystems/index.js +993 -19
  4. package/dist/runtime/subsystems/index.js.map +1 -1
  5. package/dist/runtime/subsystems/integration/entity-change-source-registry.memory.d.ts +25 -0
  6. package/dist/runtime/subsystems/integration/entity-change-source-registry.memory.js +34 -0
  7. package/dist/runtime/subsystems/integration/entity-change-source-registry.memory.js.map +1 -0
  8. package/dist/runtime/subsystems/integration/entity-change-source-registry.protocol.d.ts +53 -0
  9. package/dist/runtime/subsystems/integration/entity-change-source-registry.protocol.js +13 -0
  10. package/dist/runtime/subsystems/integration/entity-change-source-registry.protocol.js.map +1 -0
  11. package/dist/runtime/subsystems/integration/execute-integration.use-case.js.map +1 -1
  12. package/dist/runtime/subsystems/integration/index.d.ts +3 -1
  13. package/dist/runtime/subsystems/integration/index.js +35 -0
  14. package/dist/runtime/subsystems/integration/index.js.map +1 -1
  15. package/dist/runtime/subsystems/integration/integration-cursor-store.drizzle-backend.js.map +1 -1
  16. package/dist/runtime/subsystems/integration/integration-run-recorder.drizzle-backend.js.map +1 -1
  17. package/dist/runtime/subsystems/integration/integration.module.js.map +1 -1
  18. package/dist/runtime/subsystems/integration/integration.tokens.d.ts +14 -1
  19. package/dist/runtime/subsystems/integration/integration.tokens.js +2 -0
  20. package/dist/runtime/subsystems/integration/integration.tokens.js.map +1 -1
  21. package/dist/runtime/subsystems/observability/index.js.map +1 -1
  22. package/dist/runtime/subsystems/observability/observability.module.js.map +1 -1
  23. package/dist/runtime/subsystems/observability/observability.service.js.map +1 -1
  24. package/dist/src/cli/index.js +1100 -108
  25. package/dist/src/cli/index.js.map +1 -1
  26. package/dist/src/index.d.ts +48 -0
  27. package/dist/src/index.js +99 -3
  28. package/dist/src/index.js.map +1 -1
  29. package/package.json +9 -1
  30. package/runtime/subsystems/index.ts +15 -0
  31. package/runtime/subsystems/integration/entity-change-source-registry.memory.ts +40 -0
  32. package/runtime/subsystems/integration/entity-change-source-registry.protocol.ts +59 -0
  33. package/runtime/subsystems/integration/index.ts +9 -0
  34. package/runtime/subsystems/integration/integration.tokens.ts +14 -0
  35. package/templates/entity/new/clean-lite-ps/entity.ejs.t +12 -3
  36. package/templates/entity/new/clean-lite-ps/prompt-extension.js +212 -29
  37. package/templates/entity/new/backend/modules/core/integration-source.providers.ejs.t +0 -18
@@ -12,8 +12,8 @@ var __decorateClass = (decorators, target, key, kind) => {
12
12
  var __decorateParam = (index2, decorator) => (target, key) => decorator(target, key, index2);
13
13
 
14
14
  // src/cli/index.ts
15
- import { readFileSync as readFileSync6 } from "fs";
16
- import { join as join10 } from "path";
15
+ import { readFileSync as readFileSync9 } from "fs";
16
+ import { join as join13 } from "path";
17
17
  import { Builtins, Cli, Command as Command13 } from "clipanion";
18
18
 
19
19
  // src/cli/noun-module.ts
@@ -2465,6 +2465,36 @@ var EntityDefinitionSchema = z3.object({
2465
2465
  // appear in `integration.providers` — see the superRefine on
2466
2466
  // `EntityDefinitionSchema` below.
2467
2467
  detection: z3.record(z3.string(), DetectionConfigSchema).optional(),
2468
+ // RFC-0001 §1/§8: the integration *surface* this entity belongs to
2469
+ // (e.g. 'calendar', 'mail', 'crm'). Surfaces span provider contexts
2470
+ // (ADR-0006) — one Google OAuth feeds calendar+mail+transcript. The union
2471
+ // of `surface:` values across all entity YAML is the closed set that a
2472
+ // provider's `surfaces:` must be a subset of (cross-checked in
2473
+ // src/parser/validate-providers.ts). Optional: entities without an
2474
+ // integration surface omit it. The surface-package *emission* convention
2475
+ // is Track C (#329); this field is only the declarative input both tracks
2476
+ // read.
2477
+ surface: z3.string().optional(),
2478
+ // Bounded-context declaration (ADR-0004) — "which bounded context this
2479
+ // entity belongs to". This is the DURABLE decision; it is a plain
2480
+ // bounded-context slug, NOT a folder knob. Different features consume it:
2481
+ //
2482
+ // - #403 (this PR, the FIRST consumer): drives the generated code's
2483
+ // module output folder. clean-lite-ps nests the entity's module under
2484
+ // `<modules>/<context>/<entity>/` so same-context entities group
2485
+ // together; untagged entities stay flat (`<modules>/<entity>/`).
2486
+ // - ADR-0004 (deferred): a later `naming: prefix | schema` knob reads
2487
+ // this SAME field to drive the Postgres physical layout —
2488
+ // `prefix` → `pgTable('<context>__<table>')`, then the flip to
2489
+ // `schema` → `pgSchema('<context>').table('<table>')`. NOT wired here;
2490
+ // #403 makes no table/column/schema changes.
2491
+ //
2492
+ // Sibling to `surface:` and orthogonal to it (ADR-0006): context = model
2493
+ // cohesion (which domain), surface = vendor composition (which integration).
2494
+ context: z3.string().regex(
2495
+ /^[a-z][a-z0-9_]*$/,
2496
+ "context must be lowercase snake_case (e.g. 'integration')"
2497
+ ).optional(),
2468
2498
  // v2: Domain event declarations (CODEGEN-EVOLUTION-PLAN Phase 2)
2469
2499
  // Generates typed event classes, handlers, and queue registration
2470
2500
  events: z3.array(EventDeclarationSchema).optional(),
@@ -2483,7 +2513,18 @@ var EntityDefinitionSchema = z3.object({
2483
2513
  ).optional(),
2484
2514
  // v2: Analytics / semantic layer configuration
2485
2515
  // Cube.js measure packs, custom cube name, and metric definitions
2486
- analytics: AnalyticsBlockSchema.optional()
2516
+ analytics: AnalyticsBlockSchema.optional(),
2517
+ // Composite (multi-column) unique indexes (#356). Single-column uniqueness
2518
+ // is `unique: true` on the field itself; this declares constraints that
2519
+ // span 2+ columns, e.g. UNIQUE (conversation_id, sequence). Emitted as a
2520
+ // `uniqueIndex(...).on(...)` entry in the generated entity's pgTable
2521
+ // extra-config callback. `name` defaults to <table>_<col1>_<col2>_uniq.
2522
+ unique_indexes: z3.array(
2523
+ z3.object({
2524
+ fields: z3.array(z3.string()).min(2, "unique_indexes entries span 2+ columns \u2014 use `unique: true` on the field for single-column uniqueness"),
2525
+ name: z3.string().optional()
2526
+ }).strict()
2527
+ ).optional()
2487
2528
  }).strict().refine(
2488
2529
  (data) => !data.eav_value_table || typeof data.eav_definition_table === "string",
2489
2530
  {
@@ -2910,6 +2951,60 @@ var JunctionDefinitionSchema = z6.object({
2910
2951
  }
2911
2952
  );
2912
2953
 
2954
+ // src/schema/provider-definition.schema.ts
2955
+ import { z as z7 } from "zod";
2956
+ var IMPORT_REF_RE = /^[^#\s]+#[A-Za-z_$][A-Za-z0-9_$]*$/;
2957
+ var ImportRefSchema = z7.string().regex(
2958
+ IMPORT_REF_RE,
2959
+ "must be an 'import-path#Export' reference (e.g. '@app/foo/bar.strategy#BarStrategy')"
2960
+ );
2961
+ function parseImportRef(ref) {
2962
+ const hash = ref.indexOf("#");
2963
+ return { path: ref.slice(0, hash), exportName: ref.slice(hash + 1) };
2964
+ }
2965
+ var AuthTypeSchema = z7.enum(["oauth2", "api-key", "app-password"]);
2966
+ var AuthSchema = z7.object({
2967
+ type: AuthTypeSchema,
2968
+ // Class implementing the auth subsystem's strategy contract (ADR-031).
2969
+ // Pre-flight verified against a real export at codegen time.
2970
+ strategy: ImportRefSchema,
2971
+ // Required (and non-empty) iff type === 'oauth2'; see refine below.
2972
+ scopes: z7.array(z7.string()).optional()
2973
+ }).strict().refine(
2974
+ (a) => a.type !== "oauth2" || a.scopes !== void 0 && a.scopes.length > 0,
2975
+ {
2976
+ message: "auth.scopes is required and must be non-empty when auth.type is 'oauth2'",
2977
+ path: ["scopes"]
2978
+ }
2979
+ );
2980
+ var ClientSchema = z7.object({
2981
+ // API client class. Pre-flight verified against a real export.
2982
+ class: ImportRefSchema,
2983
+ base_url: z7.string().url("client.base_url must be an absolute URL")
2984
+ }).strict();
2985
+ var ProviderDefinitionSchema = z7.object({
2986
+ // Provider id — the canonical string used as detection: keys, audit rows,
2987
+ // subscription rows. kebab/lower; unique across definitions/providers/
2988
+ // (uniqueness is a cross-file check in validate-providers.ts).
2989
+ slug: z7.string().regex(
2990
+ /^[a-z][a-z0-9-]*$/,
2991
+ "slug must be kebab-case lower (e.g. 'google', 'hubspot')"
2992
+ ),
2993
+ display_name: z7.string().optional(),
2994
+ auth: AuthSchema,
2995
+ client: ClientSchema,
2996
+ // Surfaces this provider serves (ADR-0006: surfaces span contexts — one
2997
+ // Google OAuth feeds calendar+mail+transcript). Each must reference a real
2998
+ // `surface:` declared on some entity; that cross-check is in
2999
+ // validate-providers.ts. Non-empty enforced here.
3000
+ surfaces: z7.array(z7.string()).min(1, "surfaces must list at least one surface"),
3001
+ // Optional auth lifecycle hints consumed by provider-module emission (D2).
3002
+ // `refresh_behavior` is left as a free string in D1 — its domain firms up
3003
+ // when D2 consumes it; carrying it now keeps the YAML lossless.
3004
+ token_lifetime: z7.number().int().positive().optional(),
3005
+ refresh_behavior: z7.string().optional()
3006
+ }).strict();
3007
+
2913
3008
  // src/utils/yaml-loader.ts
2914
3009
  function loadEntityFromYaml(filePath) {
2915
3010
  if (!existsSync6(filePath)) {
@@ -2963,6 +3058,19 @@ function formatZodErrors(error) {
2963
3058
  return `${err.message} ${location}`;
2964
3059
  });
2965
3060
  }
3061
+ function loadEntitiesFromYaml(filePaths) {
3062
+ const successes = [];
3063
+ const failures = [];
3064
+ for (const filePath of filePaths) {
3065
+ const result = loadEntityFromYaml(filePath);
3066
+ if (result.success) {
3067
+ successes.push(result);
3068
+ } else {
3069
+ failures.push(result);
3070
+ }
3071
+ }
3072
+ return { successes, failures };
3073
+ }
2966
3074
  function loadRelationshipFromYaml(filePath) {
2967
3075
  if (!existsSync6(filePath)) {
2968
3076
  return {
@@ -3098,6 +3206,64 @@ function loadJunctionFromYaml(filePath) {
3098
3206
  filePath
3099
3207
  };
3100
3208
  }
3209
+ function loadProviderFromYaml(filePath) {
3210
+ if (!existsSync6(filePath)) {
3211
+ return {
3212
+ success: false,
3213
+ error: `File not found: ${filePath}`,
3214
+ filePath
3215
+ };
3216
+ }
3217
+ let content;
3218
+ try {
3219
+ content = readFileSync4(filePath, "utf-8");
3220
+ } catch (err) {
3221
+ return {
3222
+ success: false,
3223
+ error: `Failed to read file: ${filePath}`,
3224
+ details: [err instanceof Error ? err.message : String(err)],
3225
+ filePath
3226
+ };
3227
+ }
3228
+ let parsed;
3229
+ try {
3230
+ parsed = parseYaml(content);
3231
+ } catch (err) {
3232
+ return {
3233
+ success: false,
3234
+ error: `Invalid YAML syntax in ${filePath}`,
3235
+ details: [err instanceof Error ? err.message : String(err)],
3236
+ filePath
3237
+ };
3238
+ }
3239
+ const result = ProviderDefinitionSchema.safeParse(parsed);
3240
+ if (!result.success) {
3241
+ return {
3242
+ success: false,
3243
+ error: `Validation failed for ${filePath}`,
3244
+ details: formatZodErrors(result.error),
3245
+ filePath
3246
+ };
3247
+ }
3248
+ return {
3249
+ success: true,
3250
+ definition: result.data,
3251
+ filePath
3252
+ };
3253
+ }
3254
+ function loadProvidersFromYaml(filePaths) {
3255
+ const successes = [];
3256
+ const failures = [];
3257
+ for (const filePath of filePaths) {
3258
+ const result = loadProviderFromYaml(filePath);
3259
+ if (result.success) {
3260
+ successes.push(result);
3261
+ } else {
3262
+ failures.push(result);
3263
+ }
3264
+ }
3265
+ return { successes, failures };
3266
+ }
3101
3267
  function detectYamlType(filePath) {
3102
3268
  if (!existsSync6(filePath)) return "unknown";
3103
3269
  try {
@@ -3477,6 +3643,160 @@ function resolveRelationshipReferences(relationshipDefs, entities) {
3477
3643
  return issues;
3478
3644
  }
3479
3645
 
3646
+ // src/parser/validate-providers.ts
3647
+ import { existsSync as existsSync7, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
3648
+ import { isAbsolute, join as join8, resolve as resolve3 } from "path";
3649
+ import ts from "typescript";
3650
+ function collectEntitySurfaces(entities) {
3651
+ const surfaces = /* @__PURE__ */ new Set();
3652
+ for (const e of entities) {
3653
+ if (e.surface) surfaces.add(e.surface);
3654
+ }
3655
+ return surfaces;
3656
+ }
3657
+ function resolveImportRef(ref, opts) {
3658
+ const { path: path34, exportName } = parseImportRef(ref);
3659
+ const file = resolveModuleFile(path34, opts);
3660
+ if (!file) {
3661
+ return { status: "module-not-found", resolvedFrom: opts.sourceRoot };
3662
+ }
3663
+ const content = readFileSync5(file, "utf-8");
3664
+ const { names: names2, hasWildcard } = collectExportedNames(file, content);
3665
+ if (names2.has(exportName) || hasWildcard) {
3666
+ return { status: "ok", file };
3667
+ }
3668
+ return { status: "export-not-found", file };
3669
+ }
3670
+ function resolveModuleFile(importPath, opts) {
3671
+ let base = null;
3672
+ for (const [alias, target] of Object.entries(opts.aliases ?? {})) {
3673
+ if (importPath === alias || importPath.startsWith(`${alias}/`)) {
3674
+ const rest = importPath.slice(alias.length);
3675
+ base = join8(target, rest);
3676
+ break;
3677
+ }
3678
+ }
3679
+ if (base === null) {
3680
+ base = isAbsolute(importPath) ? importPath : resolve3(opts.sourceRoot, importPath);
3681
+ }
3682
+ const candidates = [
3683
+ base,
3684
+ `${base}.ts`,
3685
+ `${base}.tsx`,
3686
+ join8(base, "index.ts"),
3687
+ join8(base, "index.tsx")
3688
+ ];
3689
+ for (const c of candidates) {
3690
+ if (existsSync7(c) && statSync3(c).isFile()) return c;
3691
+ }
3692
+ return null;
3693
+ }
3694
+ function collectExportedNames(fileName, content) {
3695
+ const sf = ts.createSourceFile(
3696
+ fileName,
3697
+ content,
3698
+ ts.ScriptTarget.Latest,
3699
+ /* setParentNodes */
3700
+ true
3701
+ );
3702
+ const names2 = /* @__PURE__ */ new Set();
3703
+ let hasWildcard = false;
3704
+ const hasExportModifier = (node) => ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false);
3705
+ sf.forEachChild((node) => {
3706
+ if (hasExportModifier(node)) {
3707
+ if ((ts.isClassDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) && node.name && ts.isIdentifier(node.name)) {
3708
+ names2.add(node.name.text);
3709
+ } else if (ts.isVariableStatement(node)) {
3710
+ for (const decl of node.declarationList.declarations) {
3711
+ if (ts.isIdentifier(decl.name)) names2.add(decl.name.text);
3712
+ }
3713
+ }
3714
+ }
3715
+ if (ts.isExportDeclaration(node)) {
3716
+ if (!node.exportClause) {
3717
+ hasWildcard = true;
3718
+ } else if (ts.isNamedExports(node.exportClause)) {
3719
+ for (const el of node.exportClause.elements) names2.add(el.name.text);
3720
+ } else if (ts.isNamespaceExport(node.exportClause)) {
3721
+ names2.add(node.exportClause.name.text);
3722
+ }
3723
+ }
3724
+ });
3725
+ return { names: names2, hasWildcard };
3726
+ }
3727
+ function validateProviders(providers, opts) {
3728
+ const issues = [];
3729
+ const knownSurfaces = new Set(opts.entitySurfaces);
3730
+ const slugToFiles = /* @__PURE__ */ new Map();
3731
+ for (const p of providers) {
3732
+ const files = slugToFiles.get(p.definition.slug) ?? [];
3733
+ files.push(p.filePath);
3734
+ slugToFiles.set(p.definition.slug, files);
3735
+ }
3736
+ for (const [slug, files] of slugToFiles) {
3737
+ if (files.length > 1) {
3738
+ for (const file of files) {
3739
+ const others = files.filter((f) => f !== file);
3740
+ issues.push({
3741
+ severity: "error",
3742
+ type: "provider_duplicate_slug",
3743
+ message: `provider slug '${slug}' is declared more than once (also in: ${others.join(", ")})`,
3744
+ path: file
3745
+ });
3746
+ }
3747
+ }
3748
+ }
3749
+ for (const { definition, filePath } of providers) {
3750
+ const { slug } = definition;
3751
+ for (const surface of definition.surfaces) {
3752
+ if (!knownSurfaces.has(surface)) {
3753
+ const known = [...knownSurfaces].sort().join(", ") || "(none declared)";
3754
+ issues.push({
3755
+ severity: "error",
3756
+ type: "provider_unknown_surface",
3757
+ message: `provider ${slug}: surface '${surface}' is not declared by any entity (known surfaces: ${known})`,
3758
+ path: filePath
3759
+ });
3760
+ }
3761
+ }
3762
+ if (!opts.skipImportCheck) {
3763
+ if (!opts.sourceRoot) {
3764
+ throw new Error(
3765
+ "validateProviders: sourceRoot is required for the import pre-flight check (or set skipImportCheck: true)"
3766
+ );
3767
+ }
3768
+ const resolveOpts = {
3769
+ sourceRoot: opts.sourceRoot,
3770
+ aliases: opts.aliases
3771
+ };
3772
+ const refs = [
3773
+ { field: "auth.strategy", ref: definition.auth.strategy },
3774
+ { field: "client.class", ref: definition.client.class }
3775
+ ];
3776
+ for (const { field, ref } of refs) {
3777
+ const result = resolveImportRef(ref, resolveOpts);
3778
+ if (result.status === "module-not-found") {
3779
+ issues.push({
3780
+ severity: "error",
3781
+ type: "provider_import_unresolved",
3782
+ message: `provider ${slug}: ${field} '${ref}' not found \u2014 module could not be resolved from ${result.resolvedFrom}`,
3783
+ path: filePath
3784
+ });
3785
+ } else if (result.status === "export-not-found") {
3786
+ const { exportName } = parseImportRef(ref);
3787
+ issues.push({
3788
+ severity: "error",
3789
+ type: "provider_import_unresolved",
3790
+ message: `provider ${slug}: ${field} '${ref}' not found \u2014 export '${exportName}' is missing from ${result.file}`,
3791
+ path: filePath
3792
+ });
3793
+ }
3794
+ }
3795
+ }
3796
+ }
3797
+ return issues;
3798
+ }
3799
+
3480
3800
  // src/analyzer/graph-builder.ts
3481
3801
  function inferCardinality(type) {
3482
3802
  switch (type) {
@@ -4290,28 +4610,28 @@ function createSuggestion(path34) {
4290
4610
 
4291
4611
  // src/analyzer/manifest.ts
4292
4612
  import { createHash } from "crypto";
4293
- import { readFileSync as readFileSync5, writeFileSync, existsSync as existsSync7, mkdirSync, readdirSync as readdirSync6, statSync as statSync3 } from "fs";
4294
- import { join as join8 } from "path";
4613
+ import { readFileSync as readFileSync6, writeFileSync, existsSync as existsSync8, mkdirSync, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
4614
+ import { join as join9 } from "path";
4295
4615
  var MANIFEST_FILE = "manifest.json";
4296
4616
  var MANIFEST_VERSION = 1;
4297
4617
  function getManifestDir() {
4298
4618
  return process.env.CODEGEN_MANIFEST_DIR || ".codegen";
4299
4619
  }
4300
4620
  function getManifestPaths(projectRoot) {
4301
- const dir = join8(projectRoot, getManifestDir());
4302
- const file = join8(dir, MANIFEST_FILE);
4621
+ const dir = join9(projectRoot, getManifestDir());
4622
+ const file = join9(dir, MANIFEST_FILE);
4303
4623
  return { dir, file };
4304
4624
  }
4305
4625
  async function computeEntityFilesHash(entitiesDir) {
4306
- if (!existsSync7(entitiesDir)) {
4626
+ if (!existsSync8(entitiesDir)) {
4307
4627
  return createHash("sha256").update("").digest("hex");
4308
4628
  }
4309
4629
  const yamlFiles = [];
4310
4630
  function walkDir(dir) {
4311
4631
  const entries = readdirSync6(dir);
4312
4632
  for (const entry of entries) {
4313
- const fullPath = join8(dir, entry);
4314
- const stat = statSync3(fullPath);
4633
+ const fullPath = join9(dir, entry);
4634
+ const stat = statSync4(fullPath);
4315
4635
  if (stat.isDirectory()) {
4316
4636
  walkDir(fullPath);
4317
4637
  } else if (stat.isFile() && (entry.endsWith(".yaml") || entry.endsWith(".yml"))) {
@@ -4323,7 +4643,7 @@ async function computeEntityFilesHash(entitiesDir) {
4323
4643
  yamlFiles.sort();
4324
4644
  const hash = createHash("sha256");
4325
4645
  for (const file of yamlFiles) {
4326
- const content = readFileSync5(file, "utf-8");
4646
+ const content = readFileSync6(file, "utf-8");
4327
4647
  hash.update(file);
4328
4648
  hash.update(content);
4329
4649
  }
@@ -4331,11 +4651,11 @@ async function computeEntityFilesHash(entitiesDir) {
4331
4651
  }
4332
4652
  function readManifest(projectRoot) {
4333
4653
  const { file } = getManifestPaths(projectRoot);
4334
- if (!existsSync7(file)) {
4654
+ if (!existsSync8(file)) {
4335
4655
  return null;
4336
4656
  }
4337
4657
  try {
4338
- const content = readFileSync5(file, "utf-8");
4658
+ const content = readFileSync6(file, "utf-8");
4339
4659
  const manifest = JSON.parse(content);
4340
4660
  if (manifest.version !== MANIFEST_VERSION) {
4341
4661
  return null;
@@ -4347,7 +4667,7 @@ function readManifest(projectRoot) {
4347
4667
  }
4348
4668
  function writeManifest(projectRoot, manifest) {
4349
4669
  const { dir, file } = getManifestPaths(projectRoot);
4350
- if (!existsSync7(dir)) {
4670
+ if (!existsSync8(dir)) {
4351
4671
  mkdirSync(dir, { recursive: true });
4352
4672
  }
4353
4673
  const content = JSON.stringify(manifest, null, 2);
@@ -5442,8 +5762,8 @@ var BasePattern = definePattern({
5442
5762
  });
5443
5763
 
5444
5764
  // src/patterns/library/junction.pattern.ts
5445
- import { z as z7 } from "zod";
5446
- var JunctionPatternConfigSchema = z7.object({}).strict();
5765
+ import { z as z8 } from "zod";
5766
+ var JunctionPatternConfigSchema = z8.object({}).strict();
5447
5767
  var JunctionPattern = definePattern({
5448
5768
  name: "Junction",
5449
5769
  description: "Explicit many-to-many junction with role + temporal + sourcing metadata",
@@ -5572,9 +5892,9 @@ function validateEntities(entitiesDir) {
5572
5892
 
5573
5893
  // src/cli/shared/hygen.ts
5574
5894
  import { execSync } from "child_process";
5575
- import { join as join9 } from "path";
5895
+ import { join as join10 } from "path";
5576
5896
  function defaultTemplateRoot() {
5577
- return join9(import.meta.dirname, "..", "..", "..", "templates");
5897
+ return join10(import.meta.dirname, "..", "..", "..", "templates");
5578
5898
  }
5579
5899
  function quoteArg(a) {
5580
5900
  if (a === "" || /[\s"'$`\\]/.test(a)) {
@@ -5698,7 +6018,9 @@ function collectEntities(entitiesDir) {
5698
6018
  const def = result.definition;
5699
6019
  entities.push({
5700
6020
  name: def.entity.name,
5701
- plural: def.entity.plural
6021
+ plural: def.entity.plural,
6022
+ // #403: top-level `context:` nests the module folder; undefined → flat.
6023
+ context: def.context
5702
6024
  });
5703
6025
  }
5704
6026
  entities.sort((a, b) => a.name.localeCompare(b.name));
@@ -5754,11 +6076,12 @@ function entityFilePaths(info, architecture, backendSrc) {
5754
6076
  const pluralKebab = toKebabCase(plural);
5755
6077
  if (architecture === "clean-lite-ps") {
5756
6078
  const prefix = backendSrc && backendSrc !== "." ? `${backendSrc}/` : "";
6079
+ const ctxSeg = info.context ? `${info.context}/` : "";
5757
6080
  return {
5758
- moduleFile: `${prefix}modules/${plural}/${plural}.module.ts`,
6081
+ moduleFile: `${prefix}modules/${ctxSeg}${plural}/${plural}.module.ts`,
5759
6082
  moduleClass: `${toPascalCase(plural)}Module`,
5760
6083
  // Drizzle entity schema lives alongside the entity file in clean-lite-ps.
5761
- schemaFile: `${prefix}modules/${plural}/${name}.entity.ts`
6084
+ schemaFile: `${prefix}modules/${ctxSeg}${plural}/${name}.entity.ts`
5762
6085
  };
5763
6086
  }
5764
6087
  return {
@@ -5879,22 +6202,22 @@ var HEADER2 = `// AUTO-GENERATED by @pattern-stack/codegen. Do not edit.
5879
6202
  function collectScopeableNames(entitiesDir) {
5880
6203
  if (!fs3.existsSync(entitiesDir)) return [];
5881
6204
  const files = findYamlFiles(entitiesDir);
5882
- const names = [];
6205
+ const names2 = [];
5883
6206
  for (const file of files) {
5884
6207
  const result = loadEntityFromYaml(file);
5885
6208
  if (!result.success) continue;
5886
6209
  if (result.definition.entity.scopeable === true) {
5887
- names.push(result.definition.entity.name);
6210
+ names2.push(result.definition.entity.name);
5888
6211
  }
5889
6212
  }
5890
- names.sort((a, b) => a.localeCompare(b));
5891
- return names;
6213
+ names2.sort((a, b) => a.localeCompare(b));
6214
+ return names2;
5892
6215
  }
5893
- function buildScopeEntityTypeContent(names) {
5894
- if (names.length === 0) {
6216
+ function buildScopeEntityTypeContent(names2) {
6217
+ if (names2.length === 0) {
5895
6218
  return HEADER2 + "\nexport type ScopeEntityType = never;\n\nexport const SCOPE_ENTITY_TYPES = [] as const;\n";
5896
6219
  }
5897
- const quoted = names.map((n) => `'${n}'`);
6220
+ const quoted = names2.map((n) => `'${n}'`);
5898
6221
  const unionLiteral = quoted.join(" | ");
5899
6222
  const tupleLiteral = quoted.join(", ");
5900
6223
  return HEADER2 + `
@@ -6305,7 +6628,7 @@ async function regenerateSubsystemBarrel(opts) {
6305
6628
  // src/cli/shared/bridge-registry-generator.ts
6306
6629
  import fs6 from "fs";
6307
6630
  import path9 from "path";
6308
- import ts from "typescript";
6631
+ import ts2 from "typescript";
6309
6632
  var HEADER4 = `// AUTO-GENERATED by @pattern-stack/codegen. Do not edit.
6310
6633
  // Run \`codegen entity new --all\` to refresh.
6311
6634
  `;
@@ -6381,34 +6704,34 @@ function findHandlerFiles(dir) {
6381
6704
  function extractTriggersFromSourceFile(sourceFile, filePath) {
6382
6705
  const triggers = [];
6383
6706
  function visit(node) {
6384
- if (ts.isClassDeclaration(node)) {
6385
- const modifiers = ts.canHaveDecorators(node) ? ts.getDecorators(node) ?? [] : [];
6707
+ if (ts2.isClassDeclaration(node)) {
6708
+ const modifiers = ts2.canHaveDecorators(node) ? ts2.getDecorators(node) ?? [] : [];
6386
6709
  for (const decorator of modifiers) {
6387
6710
  const call = decorator.expression;
6388
- if (!ts.isCallExpression(call)) continue;
6389
- if (!ts.isIdentifier(call.expression)) continue;
6711
+ if (!ts2.isCallExpression(call)) continue;
6712
+ if (!ts2.isIdentifier(call.expression)) continue;
6390
6713
  if (call.expression.text !== "JobHandler") continue;
6391
6714
  const [typeArg, metaArg] = call.arguments;
6392
- if (!typeArg || !ts.isStringLiteralLike(typeArg)) continue;
6393
- if (!metaArg || !ts.isObjectLiteralExpression(metaArg)) continue;
6715
+ if (!typeArg || !ts2.isStringLiteralLike(typeArg)) continue;
6716
+ if (!metaArg || !ts2.isObjectLiteralExpression(metaArg)) continue;
6394
6717
  const jobType = typeArg.text;
6395
6718
  const triggersProp = metaArg.properties.find(
6396
- (p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === "triggers"
6719
+ (p) => ts2.isPropertyAssignment(p) && ts2.isIdentifier(p.name) && p.name.text === "triggers"
6397
6720
  );
6398
6721
  if (!triggersProp) continue;
6399
- if (!ts.isArrayLiteralExpression(triggersProp.initializer)) continue;
6722
+ if (!ts2.isArrayLiteralExpression(triggersProp.initializer)) continue;
6400
6723
  triggersProp.initializer.elements.forEach((el, index2) => {
6401
- if (!ts.isObjectLiteralExpression(el)) return;
6724
+ if (!ts2.isObjectLiteralExpression(el)) return;
6402
6725
  const eventProp = el.properties.find(
6403
- (p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === "event"
6726
+ (p) => ts2.isPropertyAssignment(p) && ts2.isIdentifier(p.name) && p.name.text === "event"
6404
6727
  );
6405
6728
  const mapProp = el.properties.find(
6406
- (p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === "map"
6729
+ (p) => ts2.isPropertyAssignment(p) && ts2.isIdentifier(p.name) && p.name.text === "map"
6407
6730
  );
6408
6731
  const whenProp = el.properties.find(
6409
- (p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === "when"
6732
+ (p) => ts2.isPropertyAssignment(p) && ts2.isIdentifier(p.name) && p.name.text === "when"
6410
6733
  );
6411
- if (!eventProp || !ts.isStringLiteralLike(eventProp.initializer)) return;
6734
+ if (!eventProp || !ts2.isStringLiteralLike(eventProp.initializer)) return;
6412
6735
  if (!mapProp) return;
6413
6736
  const { line } = sourceFile.getLineAndCharacterOfPosition(el.getStart(sourceFile));
6414
6737
  triggers.push({
@@ -6423,7 +6746,7 @@ function extractTriggersFromSourceFile(sourceFile, filePath) {
6423
6746
  });
6424
6747
  }
6425
6748
  }
6426
- ts.forEachChild(node, visit);
6749
+ ts2.forEachChild(node, visit);
6427
6750
  }
6428
6751
  visit(sourceFile);
6429
6752
  return triggers;
@@ -6433,13 +6756,13 @@ function scanHandlerFiles(handlersDir) {
6433
6756
  const out = [];
6434
6757
  for (const filePath of files) {
6435
6758
  const text2 = fs6.readFileSync(filePath, "utf8");
6436
- const sourceFile = ts.createSourceFile(
6759
+ const sourceFile = ts2.createSourceFile(
6437
6760
  filePath,
6438
6761
  text2,
6439
- ts.ScriptTarget.Latest,
6762
+ ts2.ScriptTarget.Latest,
6440
6763
  /* setParentNodes */
6441
6764
  true,
6442
- ts.ScriptKind.TS
6765
+ ts2.ScriptKind.TS
6443
6766
  );
6444
6767
  out.push(...extractTriggersFromSourceFile(sourceFile, filePath));
6445
6768
  }
@@ -6738,8 +7061,8 @@ function emitTypeImports(imports) {
6738
7061
  }
6739
7062
  const out = [];
6740
7063
  for (const specifier of [...grouped.keys()].sort()) {
6741
- const names = [...grouped.get(specifier)].sort();
6742
- out.push(`import type { ${names.join(", ")} } from '${specifier}';`);
7064
+ const names2 = [...grouped.get(specifier)].sort();
7065
+ out.push(`import type { ${names2.join(", ")} } from '${specifier}';`);
6743
7066
  }
6744
7067
  return out;
6745
7068
  }
@@ -6752,8 +7075,8 @@ function emitValueImports(imports) {
6752
7075
  }
6753
7076
  const out = [];
6754
7077
  for (const specifier of [...grouped.keys()].sort()) {
6755
- const names = [...grouped.get(specifier)].sort();
6756
- out.push(`import { ${names.join(", ")} } from '${specifier}';`);
7078
+ const names2 = [...grouped.get(specifier)].sort();
7079
+ out.push(`import { ${names2.join(", ")} } from '${specifier}';`);
6757
7080
  }
6758
7081
  return out;
6759
7082
  }
@@ -6785,13 +7108,13 @@ function buildTokensTs(pattern) {
6785
7108
  lines.push("");
6786
7109
  for (const i of emitTypeImports(typeImports)) lines.push(i);
6787
7110
  lines.push("");
6788
- for (const { names } of registries) {
6789
- lines.push(`export const ${names.tokenConst} = Symbol('${names.tokenConst}');`);
7111
+ for (const { names: names2 } of registries) {
7112
+ lines.push(`export const ${names2.tokenConst} = Symbol('${names2.tokenConst}');`);
6790
7113
  }
6791
7114
  lines.push("");
6792
- for (const { reg, names } of registries) {
7115
+ for (const { reg, names: names2 } of registries) {
6793
7116
  lines.push(
6794
- `export type ${names.mapType} = Map<${reg.keyType}, ${reg.valueType}>;`
7117
+ `export type ${names2.mapType} = Map<${reg.keyType}, ${reg.valueType}>;`
6795
7118
  );
6796
7119
  }
6797
7120
  lines.push("");
@@ -6805,8 +7128,8 @@ function buildProvidersTs(pattern) {
6805
7128
  providerImports.push({ specifier: e.providerImport, name: e.provider });
6806
7129
  }
6807
7130
  }
6808
- const tokenValueImports = registries.map(({ names }) => names.tokenConst);
6809
- const mapTypeImports = registries.map(({ names }) => names.mapType);
7131
+ const tokenValueImports = registries.map(({ names: names2 }) => names2.tokenConst);
7132
+ const mapTypeImports = registries.map(({ names: names2 }) => names2.mapType);
6810
7133
  const typeImports = [];
6811
7134
  for (const { reg } of registries) {
6812
7135
  typeImports.push({ specifier: reg.keyTypeImport, name: reg.keyType });
@@ -6831,24 +7154,24 @@ function buildProvidersTs(pattern) {
6831
7154
  `export function build${pascal}RegistryProviders(opts?: ${optsType}): Provider[] {`
6832
7155
  );
6833
7156
  lines.push(" return [");
6834
- for (const { reg, names } of registries) {
7157
+ for (const { reg, names: names2 } of registries) {
6835
7158
  const factoryArgs = reg.entries.map((e, i) => `${camelArg(e.provider, i)}: ${e.provider}`).join(", ");
6836
7159
  lines.push(" {");
6837
- lines.push(` provide: ${names.tokenConst},`);
7160
+ lines.push(` provide: ${names2.tokenConst},`);
6838
7161
  lines.push(` useFactory: (${factoryArgs}) => {`);
6839
- lines.push(` const base: ${names.mapType} = new Map<${reg.keyType}, ${reg.valueType}>([`);
7162
+ lines.push(` const base: ${names2.mapType} = new Map<${reg.keyType}, ${reg.valueType}>([`);
6840
7163
  for (const [i, e] of reg.entries.entries()) {
6841
7164
  lines.push(
6842
7165
  ` ['${e.key}' as ${reg.keyType}, ${camelArg(e.provider, i)}],`
6843
7166
  );
6844
7167
  }
6845
7168
  lines.push(" ]);");
6846
- lines.push(` if (opts?.${names.overrideField}) {`);
6847
- lines.push(` for (const [k, v] of Object.entries(opts.${names.overrideField})) {`);
7169
+ lines.push(` if (opts?.${names2.overrideField}) {`);
7170
+ lines.push(` for (const [k, v] of Object.entries(opts.${names2.overrideField})) {`);
6848
7171
  lines.push(` if (v !== undefined) base.set(k as ${reg.keyType}, v as ${reg.valueType});`);
6849
7172
  lines.push(" }");
6850
7173
  lines.push(" }");
6851
- lines.push(` return base as ${names.mapType};`);
7174
+ lines.push(` return base as ${names2.mapType};`);
6852
7175
  lines.push(" },");
6853
7176
  const injectList = reg.entries.map((e) => e.provider).join(", ");
6854
7177
  lines.push(` inject: [${injectList}],`);
@@ -6878,8 +7201,8 @@ function buildDispatcherTs(pattern) {
6878
7201
  providerImports.push({ specifier: e.providerImport, name: e.provider });
6879
7202
  }
6880
7203
  }
6881
- const tokenValues = registries.map(({ names }) => names.tokenConst);
6882
- const mapTypes = registries.map(({ names }) => names.mapType);
7204
+ const tokenValues = registries.map(({ names: names2 }) => names2.tokenConst);
7205
+ const mapTypes = registries.map(({ names: names2 }) => names2.mapType);
6883
7206
  const lines = [];
6884
7207
  lines.push(HEADER5.trimEnd());
6885
7208
  lines.push("");
@@ -6897,21 +7220,21 @@ function buildDispatcherTs(pattern) {
6897
7220
  lines.push("@Injectable()");
6898
7221
  lines.push(`export class ${className} {`);
6899
7222
  lines.push(" constructor(");
6900
- for (const { names } of registries) {
6901
- const fieldName = names.method === "select" ? "registry" : `${camelFromMethod(names.method)}Registry`;
7223
+ for (const { names: names2 } of registries) {
7224
+ const fieldName = names2.method === "select" ? "registry" : `${camelFromMethod(names2.method)}Registry`;
6902
7225
  lines.push(
6903
- ` @Inject(${names.tokenConst}) protected readonly ${fieldName}: ${names.mapType},`
7226
+ ` @Inject(${names2.tokenConst}) protected readonly ${fieldName}: ${names2.mapType},`
6904
7227
  );
6905
7228
  }
6906
7229
  lines.push(" ) {}");
6907
7230
  lines.push("");
6908
- for (const { reg, names } of registries) {
6909
- const fieldName = names.method === "select" ? "registry" : `${camelFromMethod(names.method)}Registry`;
7231
+ for (const { reg, names: names2 } of registries) {
7232
+ const fieldName = names2.method === "select" ? "registry" : `${camelFromMethod(names2.method)}Registry`;
6910
7233
  for (const e of reg.entries) {
6911
- lines.push(` ${names.method}(key: '${e.key}'): ${e.provider};`);
7234
+ lines.push(` ${names2.method}(key: '${e.key}'): ${e.provider};`);
6912
7235
  }
6913
- lines.push(` ${names.method}(key: ${reg.keyType}): ${reg.valueType};`);
6914
- lines.push(` ${names.method}(key: ${reg.keyType}): ${reg.valueType} {`);
7236
+ lines.push(` ${names2.method}(key: ${reg.keyType}): ${reg.valueType};`);
7237
+ lines.push(` ${names2.method}(key: ${reg.keyType}): ${reg.valueType} {`);
6915
7238
  lines.push(` const entry = this.${fieldName}.get(key);`);
6916
7239
  lines.push(
6917
7240
  ` if (!entry) throw new ${errorClass}(\`Unknown ${reg.keyType}: \${String(key)}\`);`
@@ -6953,7 +7276,7 @@ function buildModuleTs(pattern) {
6953
7276
  typeImports.push({ specifier: reg.keyTypeImport, name: reg.keyType });
6954
7277
  typeImports.push({ specifier: reg.valueTypeImport, name: reg.valueType });
6955
7278
  }
6956
- const tokenValues = registries.map(({ names }) => names.tokenConst);
7279
+ const tokenValues = registries.map(({ names: names2 }) => names2.tokenConst);
6957
7280
  const lines = [];
6958
7281
  lines.push(HEADER5.trimEnd());
6959
7282
  lines.push("");
@@ -6964,9 +7287,9 @@ function buildModuleTs(pattern) {
6964
7287
  lines.push(`import { ${tokenValues.sort().join(", ")} } from './tokens.js';`);
6965
7288
  lines.push("");
6966
7289
  lines.push(`export interface ${optsType} {`);
6967
- for (const { reg, names } of registries) {
7290
+ for (const { reg, names: names2 } of registries) {
6968
7291
  lines.push(
6969
- ` ${names.overrideField}?: Partial<Record<${reg.keyType}, ${reg.valueType}>>;`
7292
+ ` ${names2.overrideField}?: Partial<Record<${reg.keyType}, ${reg.valueType}>>;`
6970
7293
  );
6971
7294
  }
6972
7295
  lines.push("}");
@@ -7080,7 +7403,7 @@ import fs8 from "fs";
7080
7403
  import path11 from "path";
7081
7404
 
7082
7405
  // src/parser/load-events.ts
7083
- import { basename as basename2, resolve as resolve3 } from "path";
7406
+ import { basename as basename2, resolve as resolve4 } from "path";
7084
7407
  function loadErrorToIssue2(error) {
7085
7408
  const issues = [];
7086
7409
  issues.push({
@@ -7110,7 +7433,7 @@ function stripYamlExt(file) {
7110
7433
  function loadEvents(eventsDir, entityNames) {
7111
7434
  const events = [];
7112
7435
  const issues = [];
7113
- const resolvedDir = resolve3(eventsDir);
7436
+ const resolvedDir = resolve4(eventsDir);
7114
7437
  let files;
7115
7438
  try {
7116
7439
  files = findYamlFiles(resolvedDir);
@@ -7752,6 +8075,564 @@ function validateEntityEmits(entities, events) {
7752
8075
  return issues;
7753
8076
  }
7754
8077
 
8078
+ // src/cli/shared/provider-module-generator.ts
8079
+ import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync7, statSync as statSync5, writeFileSync as writeFileSync2 } from "fs";
8080
+ import { dirname, isAbsolute as isAbsolute2, join as join11, resolve as resolve5 } from "path";
8081
+ function providerPascalCase(slug) {
8082
+ return slug.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
8083
+ }
8084
+ function providerConstantCase(slug) {
8085
+ return slug.replace(/-/g, "_").toUpperCase();
8086
+ }
8087
+ function providerModuleBanner(sourceYaml) {
8088
+ return `// @generated by @pattern-stack/codegen from ${sourceYaml} \u2014 DO NOT EDIT.
8089
+ // Hand edits are overwritten on re-emit. Regenerate with \`bun run codegen\`. To change this provider, edit its \`definitions/providers/*.yaml\`.`;
8090
+ }
8091
+ function generateProviderModule(def, sourceYaml) {
8092
+ const { slug } = def;
8093
+ const Pascal = providerPascalCase(slug);
8094
+ const CONST = providerConstantCase(slug);
8095
+ const strategy = parseImportRef(def.auth.strategy);
8096
+ const client = parseImportRef(def.client.class);
8097
+ const strategyToken = `${CONST}_AUTH_STRATEGY`;
8098
+ const clientToken = `${CONST}_CLIENT`;
8099
+ const surfaces = def.surfaces.join(", ");
8100
+ const title = def.display_name ? `${def.display_name} (\`${slug}\`)` : `\`${slug}\``;
8101
+ return `${providerModuleBanner(sourceYaml)}
8102
+ /**
8103
+ * Provider module for ${title}.
8104
+ *
8105
+ * Surfaces: ${surfaces}.
8106
+ *
8107
+ * Wires the declared auth strategy and API client under the provider-specific
8108
+ * DI tokens \`${strategyToken}\` and \`${clientToken}\`. Per the auth subsystem
8109
+ * contract, strategies are registered under provider-specific tokens rather
8110
+ * than a single \`AUTH_STRATEGY\`. Registry aggregation (RFC-0001 \xA73) is
8111
+ * emitted by a later codegen step.
8112
+ */
8113
+ import { Module } from '@nestjs/common';
8114
+ import { ${strategy.exportName} } from '${strategy.path}';
8115
+ import { ${client.exportName} } from '${client.path}';
8116
+
8117
+ /** DI token for the \`${slug}\` provider's auth strategy. */
8118
+ export const ${strategyToken} = Symbol('${strategyToken}');
8119
+ /** DI token for the \`${slug}\` provider's API client. */
8120
+ export const ${clientToken} = Symbol('${clientToken}');
8121
+
8122
+ @Module({
8123
+ providers: [
8124
+ ${strategy.exportName},
8125
+ ${client.exportName},
8126
+ { provide: ${strategyToken}, useExisting: ${strategy.exportName} },
8127
+ { provide: ${clientToken}, useExisting: ${client.exportName} },
8128
+ ],
8129
+ exports: [
8130
+ ${strategy.exportName},
8131
+ ${client.exportName},
8132
+ ${strategyToken},
8133
+ ${clientToken},
8134
+ ],
8135
+ })
8136
+ export class ${Pascal}ProviderModule {}
8137
+ `;
8138
+ }
8139
+ function resolveTsconfigAliases(consumerRoot) {
8140
+ const tsconfigPath = join11(consumerRoot, "tsconfig.json");
8141
+ if (!existsSync9(tsconfigPath)) return null;
8142
+ let parsed;
8143
+ try {
8144
+ parsed = JSON.parse(stripJsonComments(readFileSync7(tsconfigPath, "utf-8")));
8145
+ } catch {
8146
+ return null;
8147
+ }
8148
+ const compilerOptions = parsed.compilerOptions ?? {};
8149
+ const baseUrl = compilerOptions.baseUrl ?? ".";
8150
+ const sourceRoot = isAbsolute2(baseUrl) ? baseUrl : resolve5(consumerRoot, baseUrl);
8151
+ const aliases = {};
8152
+ for (const [pattern, targets] of Object.entries(compilerOptions.paths ?? {})) {
8153
+ if (!Array.isArray(targets) || targets.length === 0) continue;
8154
+ const aliasKey = pattern.replace(/\/\*$/, "");
8155
+ const target = targets[0].replace(/\/\*$/, "");
8156
+ aliases[aliasKey] = isAbsolute2(target) ? target : resolve5(sourceRoot, target);
8157
+ }
8158
+ return { sourceRoot, aliases };
8159
+ }
8160
+ function stripJsonComments(input) {
8161
+ return input.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
8162
+ }
8163
+ function generateProviderModules(opts) {
8164
+ const base = {
8165
+ providersDir: opts.providersDir,
8166
+ skipped: false,
8167
+ written: [],
8168
+ loadFailures: [],
8169
+ issues: []
8170
+ };
8171
+ if (!existsSync9(opts.providersDir) || !statSync5(opts.providersDir).isDirectory()) {
8172
+ return { ...base, skipped: true };
8173
+ }
8174
+ const files = findYamlFiles(opts.providersDir);
8175
+ if (files.length === 0) return { ...base, skipped: true };
8176
+ const { successes, failures } = loadProvidersFromYaml(files);
8177
+ const loaded = successes.map((s) => ({
8178
+ definition: s.definition,
8179
+ filePath: s.filePath
8180
+ }));
8181
+ const issues = failures.map((f) => ({
8182
+ severity: "error",
8183
+ type: "provider_load_failed",
8184
+ message: `${f.error}${f.details?.length ? ` \u2014 ${f.details.join("; ")}` : ""}`,
8185
+ path: f.filePath
8186
+ }));
8187
+ issues.push(
8188
+ ...validateProviders(loaded, {
8189
+ entitySurfaces: opts.entitySurfaces,
8190
+ sourceRoot: opts.sourceRoot,
8191
+ aliases: opts.aliases,
8192
+ skipImportCheck: opts.skipImportCheck
8193
+ })
8194
+ );
8195
+ if (issues.some((i) => i.severity === "error")) {
8196
+ return { ...base, loadFailures: failures, issues };
8197
+ }
8198
+ const written = [];
8199
+ for (const { definition, filePath } of loaded) {
8200
+ const sourceYaml = relativeSource(filePath);
8201
+ const content = generateProviderModule(definition, sourceYaml);
8202
+ const outPath = join11(
8203
+ opts.outputRoot,
8204
+ definition.slug,
8205
+ `${definition.slug}.provider.module.ts`
8206
+ );
8207
+ if (!opts.dryRun) {
8208
+ writeIfChanged(outPath, content);
8209
+ }
8210
+ written.push(outPath);
8211
+ }
8212
+ return { ...base, written, loadFailures: failures, issues };
8213
+ }
8214
+ function writeIfChanged(outPath, content) {
8215
+ if (existsSync9(outPath) && readFileSync7(outPath, "utf-8") === content) return;
8216
+ mkdirSync2(dirname(outPath), { recursive: true });
8217
+ writeFileSync2(outPath, content);
8218
+ }
8219
+ function relativeSource(filePath) {
8220
+ const marker = "definitions/providers/";
8221
+ const idx = filePath.lastIndexOf(marker);
8222
+ if (idx !== -1) return filePath.slice(idx);
8223
+ const slash = filePath.lastIndexOf("/");
8224
+ return slash === -1 ? filePath : `definitions/providers/${filePath.slice(slash + 1)}`;
8225
+ }
8226
+
8227
+ // src/cli/shared/adapter-emission-generator.ts
8228
+ import {
8229
+ existsSync as existsSync10,
8230
+ mkdirSync as mkdirSync3,
8231
+ readFileSync as readFileSync8,
8232
+ statSync as statSync6,
8233
+ writeFileSync as writeFileSync3
8234
+ } from "fs";
8235
+ import { dirname as dirname2, join as join12 } from "path";
8236
+ var SURFACE_REGISTRY = {
8237
+ crm: {
8238
+ packageName: "@pattern-stack/codegen-crm",
8239
+ portType: "CrmPort",
8240
+ capabilitiesType: "CrmCapabilities",
8241
+ noCapsConst: "NO_CRM_CAPABILITIES",
8242
+ l2Ports: [
8243
+ {
8244
+ prop: "fields",
8245
+ type: "IFieldDefinitionReader",
8246
+ method: "list",
8247
+ params: "(_integrationId, _entity)",
8248
+ capFlag: "fieldDefinitions"
8249
+ },
8250
+ {
8251
+ prop: "picklists",
8252
+ type: "IPicklistReader",
8253
+ method: "values",
8254
+ params: "(_integrationId, _entity, _fieldId)",
8255
+ capFlag: "picklists"
8256
+ },
8257
+ {
8258
+ prop: "associations",
8259
+ type: "IAssociationReader",
8260
+ method: "list",
8261
+ params: "(_integrationId, _fromEntity, _fromId, _toEntity)",
8262
+ capFlag: "associations"
8263
+ }
8264
+ ]
8265
+ },
8266
+ // Interaction surfaces (#416) are incremental-read with no L2 sub-ports — the
8267
+ // port composes L1 (auth + sources) + a capabilities descriptor whose only
8268
+ // field is `entities`; reads go through the change-source registry. So
8269
+ // `l2Ports: []` and the scaffold emits no L2 readers/stubs/capability flags.
8270
+ calendar: {
8271
+ packageName: "@pattern-stack/codegen-calendar",
8272
+ portType: "CalendarPort",
8273
+ capabilitiesType: "CalendarCapabilities",
8274
+ noCapsConst: "NO_CALENDAR_CAPABILITIES",
8275
+ l2Ports: []
8276
+ },
8277
+ mail: {
8278
+ packageName: "@pattern-stack/codegen-mail",
8279
+ portType: "MailPort",
8280
+ capabilitiesType: "MailCapabilities",
8281
+ noCapsConst: "NO_MAIL_CAPABILITIES",
8282
+ l2Ports: []
8283
+ },
8284
+ transcript: {
8285
+ packageName: "@pattern-stack/codegen-transcript",
8286
+ portType: "TranscriptPort",
8287
+ capabilitiesType: "TranscriptCapabilities",
8288
+ noCapsConst: "NO_TRANSCRIPT_CAPABILITIES",
8289
+ l2Ports: []
8290
+ }
8291
+ };
8292
+ var SCAFFOLD_SENTINEL = "// <CODEGEN-SCAFFOLD-V1>";
8293
+ function generatedBanner(sourceDesc) {
8294
+ return `// @generated by @pattern-stack/codegen from ${sourceDesc} \u2014 DO NOT EDIT.
8295
+ // Hand edits are overwritten on re-emit. Regenerate with \`bun run codegen\`.`;
8296
+ }
8297
+ function collectEntitiesBySurface(entities) {
8298
+ const bySurface = /* @__PURE__ */ new Map();
8299
+ for (const e of entities) {
8300
+ if (!e.surface) continue;
8301
+ const list = bySurface.get(e.surface) ?? [];
8302
+ list.push(e.entity.name);
8303
+ bySurface.set(e.surface, list);
8304
+ }
8305
+ for (const [surface, list] of bySurface) {
8306
+ bySurface.set(surface, [...list].sort());
8307
+ }
8308
+ return bySurface;
8309
+ }
8310
+ function names(providerSlug, surface) {
8311
+ const providerPascal = providerPascalCase(providerSlug);
8312
+ const providerConst = providerConstantCase(providerSlug);
8313
+ const surfacePascal = providerPascalCase(surface);
8314
+ const surfaceConst = providerConstantCase(surface);
8315
+ return {
8316
+ providerPascal,
8317
+ providerConst,
8318
+ surfacePascal,
8319
+ surfaceConst,
8320
+ adapterClass: `${providerPascal}${surfacePascal}Adapter`,
8321
+ adapterModuleClass: `${providerPascal}${surfacePascal}AdapterModule`,
8322
+ providerModuleClass: `${providerPascal}ProviderModule`,
8323
+ aggregatorClass: `${surfacePascal}AdaptersModule`,
8324
+ strategyToken: `${providerConst}_AUTH_STRATEGY`,
8325
+ clientToken: `${providerConst}_CLIENT`,
8326
+ contributionsToken: `${surfaceConst}_ADAPTER_CONTRIBUTIONS`,
8327
+ entitySourcesToken: `${surfaceConst}_ENTITY_SOURCES`
8328
+ };
8329
+ }
8330
+ function generateAdapterScaffold(def, surface, entities) {
8331
+ const spec = SURFACE_REGISTRY[surface];
8332
+ if (!spec) throw new Error(`no surface package for '${surface}'`);
8333
+ const n = names(def.slug, surface);
8334
+ const client = parseImportRef(def.client.class);
8335
+ const entitiesLiteral = entities.length ? `[${entities.map((e) => `'${e}'`).join(", ")}]` : "[]";
8336
+ const surfaceTypeImports = [
8337
+ spec.portType,
8338
+ ...spec.l2Ports.map((p) => p.type),
8339
+ spec.capabilitiesType
8340
+ ].map((t) => ` ${t},`).join("\n");
8341
+ const capabilityBody = [
8342
+ ` ...${spec.noCapsConst},`,
8343
+ ...spec.l2Ports.map((p) => ` ${p.capFlag}: true,`),
8344
+ ` entities: ${entitiesLiteral},`
8345
+ ].join("\n");
8346
+ const l2Members = spec.l2Ports.map(
8347
+ (p) => ` /** L2 \u2014 fill in the provider-specific implementation. */
8348
+ readonly ${p.prop}: ${p.type} = {
8349
+ ${p.method}: async ${p.params} => {
8350
+ throw new Error('not implemented: ${n.adapterClass}.${p.prop}.${p.method}');
8351
+ },
8352
+ };`
8353
+ ).join("\n\n");
8354
+ const l2Section = l2Members ? `
8355
+ ${l2Members}
8356
+ ` : "";
8357
+ return `${SCAFFOLD_SENTINEL}
8358
+ // Scaffolded once by @pattern-stack/codegen, then author-owned. Re-running
8359
+ // codegen detects the sentinel above and SKIPS this file \u2014 your edits are safe.
8360
+ // Source: definitions/providers/${def.slug}.yaml (surface: ${surface}).
8361
+ import { Inject, Injectable } from '@nestjs/common';
8362
+ import type {
8363
+ ${surfaceTypeImports}
8364
+ } from '${spec.packageName}';
8365
+ import { ${spec.noCapsConst} } from '${spec.packageName}';
8366
+ import type {
8367
+ IAuthStrategy,
8368
+ IChangeSource,
8369
+ IEntityChangeSourceRegistry,
8370
+ } from '@pattern-stack/codegen/subsystems';
8371
+ import type { ${client.exportName} } from '${client.path}';
8372
+ import { ${n.strategyToken}, ${n.clientToken} } from '../../../providers/${def.slug}/${def.slug}.provider.module';
8373
+ import { ${n.entitySourcesToken} } from '../../${surface}-adapters.tokens';
8374
+
8375
+ @Injectable()
8376
+ export class ${n.adapterClass} implements ${spec.portType} {
8377
+ /** Declared capabilities. \`entities\` derives from \`surface: ${surface}\` entity YAML. */
8378
+ readonly capabilities: ${spec.capabilitiesType} = {
8379
+ ${capabilityBody}
8380
+ };
8381
+
8382
+ constructor(
8383
+ @Inject(${n.strategyToken}) readonly auth: IAuthStrategy,
8384
+ @Inject(${n.clientToken}) private readonly client: ${client.exportName},
8385
+ @Inject(${n.entitySourcesToken}) readonly sources: IEntityChangeSourceRegistry,
8386
+ ) {}
8387
+ ${l2Section}
8388
+ /**
8389
+ * Per-entity change sources this adapter contributes to the ${surface}
8390
+ * registry (ADR-033 \`buildChangeSource\`), keyed by entity name. The
8391
+ * surface aggregator folds these into the \`IEntityChangeSourceRegistry\`
8392
+ * bound under \`${n.entitySourcesToken}\`. Author-owned \u2014 populate one entry
8393
+ * per entity in \`capabilities.entities\`.
8394
+ */
8395
+ readonly changeSources: Record<string, IChangeSource<unknown>> = {};
8396
+
8397
+ // surface-only methods (optional on ${spec.portType}): add here
8398
+ }
8399
+ `;
8400
+ }
8401
+ function generateAdapterModule(def, surface) {
8402
+ const n = names(def.slug, surface);
8403
+ return `${generatedBanner(`definitions/providers/${def.slug}.yaml (surface: ${surface})`)}
8404
+ import { Module } from '@nestjs/common';
8405
+ import { ${n.providerModuleClass} } from '../../../providers/${def.slug}/${def.slug}.provider.module';
8406
+ import { ${n.adapterClass} } from './${def.slug}-${surface}.adapter';
8407
+
8408
+ @Module({
8409
+ imports: [${n.providerModuleClass}],
8410
+ providers: [${n.adapterClass}],
8411
+ exports: [${n.adapterClass}],
8412
+ })
8413
+ export class ${n.adapterModuleClass} {}
8414
+ `;
8415
+ }
8416
+ function generateAdaptersBarrel(surface, providerSlugs) {
8417
+ const lines = [...providerSlugs].sort().map((slug) => {
8418
+ const n = names(slug, surface);
8419
+ return `export { ${n.adapterModuleClass} } from './${slug}/${slug}-${surface}.adapter.module';`;
8420
+ }).join("\n");
8421
+ return `${generatedBanner(`definitions/providers/*.yaml (surface: ${surface})`)}
8422
+ ${lines}
8423
+ `;
8424
+ }
8425
+ function generateSurfaceTokens(surface) {
8426
+ const n = names("__placeholder__", surface);
8427
+ return `${generatedBanner(`surface: ${surface}`)}
8428
+ import type { IChangeSource } from '@pattern-stack/codegen/subsystems';
8429
+
8430
+ /** The assembled list of every ${surface} adapter's contribution. */
8431
+ export const ${n.contributionsToken} = Symbol.for('@app/integrations/${surface}.adapter-contributions');
8432
+
8433
+ /** Resolved registry token \u2014 resolves to a C7 IEntityChangeSourceRegistry. */
8434
+ export const ${n.entitySourcesToken} = Symbol.for('@app/integrations/${surface}.entity-sources');
8435
+
8436
+ /** One provider-adapter's contribution to the surface registry. */
8437
+ export interface AdapterContribution {
8438
+ /** Provider slug. */
8439
+ provider: string;
8440
+ /** Entities this provider serves on this surface \u2192 their change sources. */
8441
+ sources: Record<string, IChangeSource<unknown>>;
8442
+ }
8443
+ `;
8444
+ }
8445
+ function generateSurfaceAggregator(surface, providerSlugs) {
8446
+ const n = names("__placeholder__", surface);
8447
+ const slugs = [...providerSlugs].sort();
8448
+ const per = slugs.map((slug) => names(slug, surface));
8449
+ const moduleClasses = per.map((p) => p.adapterModuleClass);
8450
+ const moduleImport = `import {
8451
+ ${moduleClasses.join(",\n ")},
8452
+ } from './adapters';`;
8453
+ const adapterImports = slugs.map((slug) => {
8454
+ const p = names(slug, surface);
8455
+ return `import { ${p.adapterClass} } from './adapters/${slug}/${slug}-${surface}.adapter';`;
8456
+ }).join("\n");
8457
+ const contributionEntries = slugs.map((slug) => {
8458
+ const p = names(slug, surface);
8459
+ return ` { provider: '${slug}', sources: ${lowerFirst(p.adapterClass)}.changeSources },`;
8460
+ }).join("\n");
8461
+ const injectParams = slugs.map((slug) => {
8462
+ const p = names(slug, surface);
8463
+ return `${lowerFirst(p.adapterClass)}: ${p.adapterClass}`;
8464
+ }).join(", ");
8465
+ const injectTokens = per.map((p) => p.adapterClass).join(", ");
8466
+ return `${generatedBanner(`surface: ${surface}`)}
8467
+ import { Module } from '@nestjs/common';
8468
+ import {
8469
+ MemoryEntityChangeSourceRegistry,
8470
+ type IChangeSource,
8471
+ type IEntityChangeSourceRegistry,
8472
+ } from '@pattern-stack/codegen/subsystems';
8473
+ ${moduleImport}
8474
+ ${adapterImports}
8475
+ import {
8476
+ ${n.contributionsToken},
8477
+ ${n.entitySourcesToken},
8478
+ type AdapterContribution,
8479
+ } from './${surface}-adapters.tokens';
8480
+
8481
+ /**
8482
+ * Fold every adapter contribution into one entity-keyed registry. Each entity
8483
+ * resolves to exactly one change source; two providers serving the same entity
8484
+ * is an ambiguous-source boot error.
8485
+ */
8486
+ function provide${n.surfacePascal}EntitySources(
8487
+ contribs: AdapterContribution[],
8488
+ ): IEntityChangeSourceRegistry {
8489
+ const merged = new Map<string, IChangeSource<unknown>>();
8490
+ const owner = new Map<string, string>();
8491
+ for (const contrib of contribs ?? []) {
8492
+ for (const [entity, source] of Object.entries(contrib.sources)) {
8493
+ const prior = owner.get(entity);
8494
+ if (prior !== undefined) {
8495
+ throw new Error(
8496
+ \`entity '\${entity}' is served by both '\${prior}' and '\${contrib.provider}' \u2014 ambiguous change source\`,
8497
+ );
8498
+ }
8499
+ owner.set(entity, contrib.provider);
8500
+ merged.set(entity, source);
8501
+ }
8502
+ }
8503
+ return new MemoryEntityChangeSourceRegistry(merged);
8504
+ }
8505
+
8506
+ @Module({
8507
+ imports: [${moduleClasses.join(", ")}],
8508
+ providers: [
8509
+ {
8510
+ provide: ${n.contributionsToken},
8511
+ useFactory: (${injectParams}): AdapterContribution[] => [
8512
+ ${contributionEntries}
8513
+ ],
8514
+ inject: [${injectTokens}],
8515
+ },
8516
+ {
8517
+ provide: ${n.entitySourcesToken},
8518
+ useFactory: (contributions: AdapterContribution[]) =>
8519
+ provide${n.surfacePascal}EntitySources(contributions),
8520
+ inject: [${n.contributionsToken}],
8521
+ },
8522
+ ],
8523
+ exports: [${n.entitySourcesToken}, ${n.contributionsToken}],
8524
+ })
8525
+ export class ${n.aggregatorClass} {}
8526
+ `;
8527
+ }
8528
+ function lowerFirst(s) {
8529
+ return s.length ? s[0].toLowerCase() + s.slice(1) : s;
8530
+ }
8531
+ function generateTypedView(surface, providerSlugs, entities) {
8532
+ const surfacePascal = providerPascalCase(surface);
8533
+ const slugs = [...providerSlugs].sort();
8534
+ const ents = [...entities].sort();
8535
+ const providerUnion = slugs.length ? slugs.map((s) => `'${s}'`).join(" | ") : "never";
8536
+ const entityUnion = ents.length ? ents.map((e) => `'${e}'`).join(" | ") : "never";
8537
+ const mapEntries = slugs.map((s) => ` ${jsKey(s)}: ${surfacePascal}Entity;`).join("\n");
8538
+ return `${generatedBanner(`surface: ${surface}`)}
8539
+ /**
8540
+ * Per-consumer typed view for the \`${surface}\` surface. Surface-scoped unions
8541
+ * + a (provider, entity) validity map for compile-time-checked consumer
8542
+ * use-cases. Replaces ADR-033.2's per-entity provider tuples (RFC-0001 \xA75/\xA78).
8543
+ */
8544
+
8545
+ /** Providers whose \`surfaces[]\` include \`${surface}\`. */
8546
+ export type ${surfacePascal}Provider = ${providerUnion};
8547
+
8548
+ /** Entities declared with \`surface: ${surface}\`. */
8549
+ export type ${surfacePascal}Entity = ${entityUnion};
8550
+
8551
+ /** Valid entities per provider on this surface. */
8552
+ export interface ${surfacePascal}ProviderEntities {
8553
+ ${mapEntries || " // no providers serve this surface yet"}
8554
+ }
8555
+ `;
8556
+ }
8557
+ function jsKey(key) {
8558
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : `'${key}'`;
8559
+ }
8560
+ function emitAdapters(opts) {
8561
+ const result = {
8562
+ written: [],
8563
+ scaffoldsWritten: [],
8564
+ scaffoldsSkipped: [],
8565
+ skippedSurfaces: []
8566
+ };
8567
+ const entitiesBySurface = collectEntitiesBySurface(opts.entities);
8568
+ const bySurface = /* @__PURE__ */ new Map();
8569
+ for (const { definition } of opts.providers) {
8570
+ for (const surface of definition.surfaces) {
8571
+ if (!SURFACE_REGISTRY[surface]) {
8572
+ result.skippedSurfaces.push({
8573
+ provider: definition.slug,
8574
+ surface,
8575
+ reason: `no surface package for '${surface}' yet \u2014 add codegen-${surface} (Track C) to emit its adapters`
8576
+ });
8577
+ continue;
8578
+ }
8579
+ const list = bySurface.get(surface) ?? [];
8580
+ list.push(definition.slug);
8581
+ bySurface.set(surface, list);
8582
+ }
8583
+ }
8584
+ const defBySlug = new Map(opts.providers.map((p) => [p.definition.slug, p.definition]));
8585
+ for (const [surface, slugs] of bySurface) {
8586
+ const surfaceDir = join12(opts.outputRoot, surface);
8587
+ const adaptersDir = join12(surfaceDir, "adapters");
8588
+ for (const slug of slugs) {
8589
+ const def = defBySlug.get(slug);
8590
+ const providerDir = join12(adaptersDir, slug);
8591
+ const scaffoldPath = join12(providerDir, `${slug}-${surface}.adapter.ts`);
8592
+ const modulePath = join12(providerDir, `${slug}-${surface}.adapter.module.ts`);
8593
+ if (existsSync10(scaffoldPath)) {
8594
+ result.scaffoldsSkipped.push(scaffoldPath);
8595
+ } else {
8596
+ const content = generateAdapterScaffold(
8597
+ def,
8598
+ surface,
8599
+ entitiesBySurface.get(surface) ?? []
8600
+ );
8601
+ if (!opts.dryRun) writeFile(scaffoldPath, content);
8602
+ result.scaffoldsWritten.push(scaffoldPath);
8603
+ }
8604
+ const moduleContent = generateAdapterModule(def, surface);
8605
+ if (!opts.dryRun) writeIfChanged2(modulePath, moduleContent);
8606
+ result.written.push(modulePath);
8607
+ }
8608
+ const barrelPath = join12(adaptersDir, "index.ts");
8609
+ const tokensPath = join12(surfaceDir, `${surface}-adapters.tokens.ts`);
8610
+ const aggregatorPath = join12(surfaceDir, `${surface}-adapters.module.ts`);
8611
+ const typedViewPath = join12(surfaceDir, "types.generated.ts");
8612
+ const files = [
8613
+ [barrelPath, generateAdaptersBarrel(surface, slugs)],
8614
+ [tokensPath, generateSurfaceTokens(surface)],
8615
+ [aggregatorPath, generateSurfaceAggregator(surface, slugs)],
8616
+ [typedViewPath, generateTypedView(surface, slugs, entitiesBySurface.get(surface) ?? [])]
8617
+ ];
8618
+ for (const [path34, content] of files) {
8619
+ if (!opts.dryRun) writeIfChanged2(path34, content);
8620
+ result.written.push(path34);
8621
+ }
8622
+ }
8623
+ return result;
8624
+ }
8625
+ function writeFile(outPath, content) {
8626
+ mkdirSync3(dirname2(outPath), { recursive: true });
8627
+ writeFileSync3(outPath, content);
8628
+ }
8629
+ function writeIfChanged2(outPath, content) {
8630
+ if (existsSync10(outPath) && statSync6(outPath).isFile() && readFileSync8(outPath, "utf-8") === content) {
8631
+ return;
8632
+ }
8633
+ writeFile(outPath, content);
8634
+ }
8635
+
7755
8636
  // src/cli/shared/events-path.ts
7756
8637
  import path12 from "path";
7757
8638
  var FALLBACK = "events";
@@ -7859,20 +8740,45 @@ async function hints(ctx) {
7859
8740
  }
7860
8741
  ];
7861
8742
  }
7862
- return [
8743
+ const baseHints = [
7863
8744
  { command: "codegen entity new <file>", description: "Generate one entity" },
7864
8745
  { command: "codegen entity new --all", description: "Regenerate all entities" },
7865
8746
  { command: "codegen entity validate", description: "Validate YAML definitions" },
7866
8747
  { command: "codegen entity list", description: "List entities as a table" }
7867
8748
  ];
8749
+ const providersDir = ctx.config?.paths?.providers != null ? path13.resolve(
8750
+ ctx.cwd,
8751
+ ctx.config.paths.providers
8752
+ ) : path13.resolve(ctx.cwd, "definitions/providers");
8753
+ if (fs9.existsSync(providersDir)) {
8754
+ baseHints.push({
8755
+ command: "codegen entity new --all",
8756
+ description: "Regenerate provider modules + adapter scaffolds (Track D)"
8757
+ });
8758
+ }
8759
+ return baseHints;
7868
8760
  }
7869
8761
  var EntityNewCommand = class extends Command2 {
7870
8762
  static paths = [["entity", "new"]];
7871
8763
  static usage = Command2.Usage({
7872
8764
  description: "Generate code for one or more entities from YAML",
8765
+ details: `
8766
+ Generates Clean Architecture code for the named entity (or all entities with \`--all\`), then runs the post-generation codegen steps that share this entrypoint:
8767
+
8768
+ - **Event codegen** \u2014 \`AppDomainEvent\` union + typed bus from \`events/*.yaml\`.
8769
+ - **Bridge registry** \u2014 when the bridge subsystem is installed.
8770
+ - **Orchestration modules** \u2014 from orchestration patterns.
8771
+ - **Provider + adapter codegen (Track D, RFC-0001)** \u2014 when \`definitions/providers/*.yaml\` exist, emits one provider module per file into \`<backendSrc>/integrations/providers/\` and the matching adapter scaffolds + \`@generated\` files into \`<backendSrc>/integrations/\`. Author-owned scaffolds are emit-once (never overwritten); \`@generated\` files re-emit each run. With no providers dir the step is silently skipped.
8772
+
8773
+ There is no separate \`provider\`, \`integration\`, or \`gen\` command \u2014 Track D codegen is driven entirely by re-running \`entity new\`. The \`just gen\` / \`just gen-all\` recipes are thin wrappers over it.
8774
+ `,
7873
8775
  examples: [
7874
8776
  ["Generate a single entity", "codegen entity new entities/contact.yaml"],
7875
8777
  ["Regenerate all entities", "codegen entity new --all"],
8778
+ [
8779
+ "Regenerate everything incl. provider/adapter codegen",
8780
+ "codegen entity new --all"
8781
+ ],
7876
8782
  ["Preview without writing", "codegen entity new entities/contact.yaml --dry-run"]
7877
8783
  ]
7878
8784
  });
@@ -8261,6 +9167,92 @@ var EntityNewCommand = class extends Command2 {
8261
9167
  }
8262
9168
  }
8263
9169
  }
9170
+ let providerResult = null;
9171
+ try {
9172
+ const providersDir = ctx.config?.paths?.providers != null ? path13.resolve(
9173
+ ctx.cwd,
9174
+ ctx.config.paths.providers
9175
+ ) : path13.resolve(ctx.cwd, "definitions/providers");
9176
+ const providerOutputRoot = path13.resolve(
9177
+ ctx.cwd,
9178
+ backendSrcForHandlers,
9179
+ "integrations/providers"
9180
+ );
9181
+ const entitySurfaces = fs9.existsSync(entitiesDir) ? collectEntitySurfaces(
9182
+ loadEntitiesFromYaml(findYamlFiles(entitiesDir)).successes.map(
9183
+ (s) => s.definition
9184
+ )
9185
+ ) : /* @__PURE__ */ new Set();
9186
+ const tsAliases = resolveTsconfigAliases(ctx.cwd);
9187
+ providerResult = generateProviderModules({
9188
+ providersDir,
9189
+ outputRoot: providerOutputRoot,
9190
+ entitySurfaces,
9191
+ sourceRoot: tsAliases?.sourceRoot,
9192
+ aliases: tsAliases?.aliases,
9193
+ skipImportCheck: tsAliases === null
9194
+ });
9195
+ if (!providerResult.skipped && !isJsonMode()) {
9196
+ for (const issue of providerResult.issues) {
9197
+ printError(`provider codegen: ${issue.message}`);
9198
+ }
9199
+ if (providerResult.issues.length === 0) {
9200
+ printInfo(
9201
+ `provider modules regenerated (${providerResult.written.length}) \u2192 ${providerOutputRoot}`
9202
+ );
9203
+ }
9204
+ }
9205
+ if (providerResult.issues.some((i) => i.severity === "error") && !this.continueOnError) {
9206
+ return 1;
9207
+ }
9208
+ } catch (err) {
9209
+ const msg = err instanceof Error ? err.message : String(err);
9210
+ if (!isJsonMode()) {
9211
+ printWarning(`provider codegen failed \u2014 ${msg}`);
9212
+ }
9213
+ }
9214
+ try {
9215
+ if (providerResult && !providerResult.skipped && providerResult.issues.length === 0) {
9216
+ const providersDir = providerResult.providersDir;
9217
+ const adapterOutputRoot = path13.resolve(
9218
+ ctx.cwd,
9219
+ backendSrcForHandlers,
9220
+ "integrations"
9221
+ );
9222
+ const entityDefs = fs9.existsSync(entitiesDir) ? loadEntitiesFromYaml(findYamlFiles(entitiesDir)).successes.map(
9223
+ (s) => s.definition
9224
+ ) : [];
9225
+ const loadedProviders = loadProvidersFromYaml(
9226
+ findYamlFiles(providersDir)
9227
+ ).successes.map((s) => ({
9228
+ definition: s.definition,
9229
+ filePath: s.filePath
9230
+ }));
9231
+ const adapterResult = emitAdapters({
9232
+ providers: loadedProviders,
9233
+ entities: entityDefs,
9234
+ outputRoot: adapterOutputRoot
9235
+ });
9236
+ if (!isJsonMode()) {
9237
+ if (adapterResult.written.length || adapterResult.scaffoldsWritten.length) {
9238
+ printInfo(
9239
+ `adapter codegen: ${adapterResult.scaffoldsWritten.length} scaffold(s) + ${adapterResult.written.length} @generated \u2192 ${adapterOutputRoot}`
9240
+ );
9241
+ }
9242
+ for (const s of adapterResult.scaffoldsSkipped) {
9243
+ printInfo(`skipped scaffold ${s} (author-owned)`);
9244
+ }
9245
+ for (const s of adapterResult.skippedSurfaces) {
9246
+ printWarning(`adapter codegen: ${s.reason} (provider ${s.provider})`);
9247
+ }
9248
+ }
9249
+ }
9250
+ } catch (err) {
9251
+ const msg = err instanceof Error ? err.message : String(err);
9252
+ if (!isJsonMode()) {
9253
+ printWarning(`adapter codegen failed \u2014 ${msg}`);
9254
+ }
9255
+ }
8264
9256
  if (isJsonMode()) {
8265
9257
  printJson({
8266
9258
  command: "entity new",
@@ -10580,7 +11572,7 @@ var REQUIRED_ALIASES = {
10580
11572
  "@modules/*": ["./src/modules/*"],
10581
11573
  "@generated/*": ["./src/generated/*"]
10582
11574
  };
10583
- function stripJsonComments(raw) {
11575
+ function stripJsonComments2(raw) {
10584
11576
  let out = "";
10585
11577
  let i = 0;
10586
11578
  let inString = false;
@@ -10628,7 +11620,7 @@ var REQUIRED_COMPILER_OPTIONS = {
10628
11620
  function mergeTsconfig(raw) {
10629
11621
  let parsed;
10630
11622
  try {
10631
- parsed = JSON.parse(stripJsonComments(raw));
11623
+ parsed = JSON.parse(stripJsonComments2(raw));
10632
11624
  } catch (err) {
10633
11625
  return {
10634
11626
  content: raw,
@@ -11098,8 +12090,8 @@ function ensureMainSwaggerBlock(sourceFile, opts) {
11098
12090
  /^import\s+\{\s*([^}]+)\s*\}\s+from\s+['"]([^'"]+)['"]\s*;?\s*$/
11099
12091
  );
11100
12092
  if (match) {
11101
- const names = match[1].split(",").map((s) => s.trim()).filter(Boolean);
11102
- ensureImport(sourceFile, match[2], names);
12093
+ const names2 = match[1].split(",").map((s) => s.trim()).filter(Boolean);
12094
+ ensureImport(sourceFile, match[2], names2);
11103
12095
  } else {
11104
12096
  sourceFile.insertStatements(0, importLine);
11105
12097
  }
@@ -12105,10 +13097,10 @@ var ProjectInitCommand = class extends Command7 {
12105
13097
  };
12106
13098
  function askConfirm(question) {
12107
13099
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
12108
- return new Promise((resolve4) => {
13100
+ return new Promise((resolve6) => {
12109
13101
  rl.question(`${question} [Y/n] `, (answer) => {
12110
13102
  rl.close();
12111
- resolve4(answer.trim().toLowerCase() !== "n");
13103
+ resolve6(answer.trim().toLowerCase() !== "n");
12112
13104
  });
12113
13105
  });
12114
13106
  }
@@ -13715,7 +14707,7 @@ var junction_default = junctionNoun;
13715
14707
  // src/cli/commands/events.ts
13716
14708
  import fs20 from "fs";
13717
14709
  import path32 from "path";
13718
- import ts2 from "typescript";
14710
+ import ts3 from "typescript";
13719
14711
  import { Command as Command11, Option as Option11 } from "clipanion";
13720
14712
  function scanSourceFileForConsumers(sourceFile, filePath, eventType) {
13721
14713
  const tier2 = [];
@@ -13729,13 +14721,13 @@ function scanSourceFileForConsumers(sourceFile, filePath, eventType) {
13729
14721
  }
13730
14722
  function checkImport(node) {
13731
14723
  const moduleSpec = node.moduleSpecifier;
13732
- if (ts2.isStringLiteral(moduleSpec) && moduleSpec.text.includes("subsystems/bridge")) {
14724
+ if (ts3.isStringLiteral(moduleSpec) && moduleSpec.text.includes("subsystems/bridge")) {
13733
14725
  hasEventFlowImport = true;
13734
14726
  }
13735
14727
  const clause = node.importClause;
13736
14728
  if (!clause) return;
13737
14729
  const named = clause.namedBindings;
13738
- if (named && ts2.isNamedImports(named)) {
14730
+ if (named && ts3.isNamedImports(named)) {
13739
14731
  for (const el of named.elements) {
13740
14732
  const name = el.name.text;
13741
14733
  if (name === "EventFlowService" || name === "EVENT_FLOW") {
@@ -13745,11 +14737,11 @@ function scanSourceFileForConsumers(sourceFile, filePath, eventType) {
13745
14737
  }
13746
14738
  }
13747
14739
  function checkCall(node) {
13748
- if (!ts2.isPropertyAccessExpression(node.expression)) return;
14740
+ if (!ts3.isPropertyAccessExpression(node.expression)) return;
13749
14741
  const methodName = node.expression.name.text;
13750
14742
  if (methodName !== "publishAndStart" && methodName !== "subscribe") return;
13751
14743
  const firstArg = node.arguments[0];
13752
- if (!firstArg || !ts2.isStringLiteralLike(firstArg)) return;
14744
+ if (!firstArg || !ts3.isStringLiteralLike(firstArg)) return;
13753
14745
  if (firstArg.text !== eventType) return;
13754
14746
  const receiverText = node.expression.expression.getText(sourceFile);
13755
14747
  if (methodName === "publishAndStart") {
@@ -13770,24 +14762,24 @@ function scanSourceFileForConsumers(sourceFile, filePath, eventType) {
13770
14762
  function findEnclosingClassName(node) {
13771
14763
  let cur = node.parent;
13772
14764
  while (cur) {
13773
- if (ts2.isClassDeclaration(cur) && cur.name) return cur.name.text;
13774
- if (ts2.isClassExpression(cur) && cur.name) return cur.name.text;
14765
+ if (ts3.isClassDeclaration(cur) && cur.name) return cur.name.text;
14766
+ if (ts3.isClassExpression(cur) && cur.name) return cur.name.text;
13775
14767
  cur = cur.parent;
13776
14768
  }
13777
14769
  return null;
13778
14770
  }
13779
14771
  function checkDecoratorOn(member) {
13780
- const decorators = ts2.canHaveDecorators(member) ? ts2.getDecorators(member) ?? [] : [];
14772
+ const decorators = ts3.canHaveDecorators(member) ? ts3.getDecorators(member) ?? [] : [];
13781
14773
  for (const decorator of decorators) {
13782
14774
  const call = decorator.expression;
13783
- if (!ts2.isCallExpression(call)) continue;
13784
- if (!ts2.isIdentifier(call.expression)) continue;
14775
+ if (!ts3.isCallExpression(call)) continue;
14776
+ if (!ts3.isIdentifier(call.expression)) continue;
13785
14777
  if (call.expression.text !== "OnEvent") continue;
13786
14778
  const firstArg = call.arguments[0];
13787
- if (!firstArg || !ts2.isStringLiteralLike(firstArg)) continue;
14779
+ if (!firstArg || !ts3.isStringLiteralLike(firstArg)) continue;
13788
14780
  if (firstArg.text !== eventType) continue;
13789
14781
  const className = findEnclosingClassName(member) ?? "<anonymous>";
13790
- const memberName = ts2.isIdentifier(member.name) ? member.name.text : member.name.getText(sourceFile);
14782
+ const memberName = ts3.isIdentifier(member.name) ? member.name.text : member.name.getText(sourceFile);
13791
14783
  tier1.push({
13792
14784
  kind: "on-event",
13793
14785
  siteLabel: `${className}.${memberName} @OnEvent('${eventType}')`,
@@ -13797,12 +14789,12 @@ function scanSourceFileForConsumers(sourceFile, filePath, eventType) {
13797
14789
  }
13798
14790
  }
13799
14791
  function visit(node) {
13800
- if (ts2.isImportDeclaration(node)) checkImport(node);
13801
- if (ts2.isCallExpression(node)) checkCall(node);
13802
- if (ts2.isMethodDeclaration(node) || ts2.isPropertyDeclaration(node)) {
14792
+ if (ts3.isImportDeclaration(node)) checkImport(node);
14793
+ if (ts3.isCallExpression(node)) checkCall(node);
14794
+ if (ts3.isMethodDeclaration(node) || ts3.isPropertyDeclaration(node)) {
13803
14795
  checkDecoratorOn(node);
13804
14796
  }
13805
- ts2.forEachChild(node, visit);
14797
+ ts3.forEachChild(node, visit);
13806
14798
  }
13807
14799
  visit(sourceFile);
13808
14800
  return { tier2, tier1, hasEventFlowImport };
@@ -13814,12 +14806,12 @@ function scanDirectoryForConsumers(rootDir, eventType) {
13814
14806
  let hasEventFlowImport = false;
13815
14807
  for (const filePath of files) {
13816
14808
  const text2 = fs20.readFileSync(filePath, "utf8");
13817
- const sourceFile = ts2.createSourceFile(
14809
+ const sourceFile = ts3.createSourceFile(
13818
14810
  filePath,
13819
14811
  text2,
13820
- ts2.ScriptTarget.Latest,
14812
+ ts3.ScriptTarget.Latest,
13821
14813
  true,
13822
- ts2.ScriptKind.TS
14814
+ ts3.ScriptKind.TS
13823
14815
  );
13824
14816
  const result = scanSourceFileForConsumers(sourceFile, filePath, eventType);
13825
14817
  tier2.push(...result.tier2);
@@ -14233,12 +15225,12 @@ async function summary9(ctx) {
14233
15225
  await reloadRegistry(ctx);
14234
15226
  } catch {
14235
15227
  }
14236
- const names = getOrchestrationPatternNames();
15228
+ const names2 = getOrchestrationPatternNames();
14237
15229
  return {
14238
15230
  title: "orchestration",
14239
15231
  body: [
14240
- `patterns: ${names.length}`,
14241
- ...names.length > 0 ? [` ${names.join(", ")}`] : []
15232
+ `patterns: ${names2.length}`,
15233
+ ...names2.length > 0 ? [` ${names2.join(", ")}`] : []
14242
15234
  ]
14243
15235
  };
14244
15236
  }
@@ -14287,8 +15279,8 @@ var update_default = UpdateShortcut;
14287
15279
  // src/cli/index.ts
14288
15280
  function readVersion() {
14289
15281
  try {
14290
- const pkgPath = join10(import.meta.dirname, "..", "..", "package.json");
14291
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
15282
+ const pkgPath = join13(import.meta.dirname, "..", "..", "package.json");
15283
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
14292
15284
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
14293
15285
  } catch {
14294
15286
  return "0.0.0";