@dereekb/dbx-components-mcp 13.43.0 → 14.0.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.
@@ -10,7 +10,7 @@ var __export = (target, all) => {
10
10
  // packages/dbx-components-mcp/package.json
11
11
  var package_default = {
12
12
  name: "@dereekb/dbx-components-mcp",
13
- version: "13.43.0",
13
+ version: "14.0.1",
14
14
  type: "module",
15
15
  bin: {
16
16
  "dbx-components-mcp": "dbx-components-mcp.js"
@@ -21,8 +21,8 @@ var package_default = {
21
21
  "ts-morph": "^21.0.0"
22
22
  },
23
23
  peerDependencies: {
24
- "@dereekb/dbx-cli": "13.43.0",
25
- "@dereekb/util": "13.43.0"
24
+ "@dereekb/dbx-cli": "14.0.1",
25
+ "@dereekb/util": "14.0.1"
26
26
  }
27
27
  };
28
28
 
@@ -706,7 +706,7 @@ function tallyProp(input) {
706
706
  }
707
707
  function scoreAgainstEntry(input, entry) {
708
708
  const tally = { matched: [], extraEntry: [], missingInput: [], weightedIntersect: 0, weightedUnion: 0 };
709
- const allProps = /* @__PURE__ */ new Set([...input.keys(), ...entry.keys()]);
709
+ const allProps = /* @__PURE__ */ new Set([...Array.from(input.keys()), ...Array.from(entry.keys())]);
710
710
  for (const prop of allProps) {
711
711
  tallyProp({ tally, prop, inputValue: input.get(prop), entryValue: entry.get(prop) });
712
712
  }
@@ -2139,7 +2139,15 @@ var FIREBASE_MODELS = [
2139
2139
  converter: "optionalFirestoreDate()",
2140
2140
  tsType: "Maybe<Date>",
2141
2141
  optional: true,
2142
- description: "The date the space was submitted, if it was. Its presence IS the lock."
2142
+ description: "The date the CURRENT submission was made, if the space is submitted. Its presence IS the lock."
2143
+ },
2144
+ {
2145
+ name: "fsat",
2146
+ longName: "firstSubmittedAt",
2147
+ converter: "optionalFirestoreDate()",
2148
+ tsType: "Maybe<Date>",
2149
+ optional: true,
2150
+ description: "The date the space was FIRST submitted, if it ever was."
2143
2151
  },
2144
2152
  {
2145
2153
  name: "cpat",
@@ -2156,6 +2164,46 @@ var FIREBASE_MODELS = [
2156
2164
  tsType: "Maybe<Date>",
2157
2165
  optional: true,
2158
2166
  description: "The date this space becomes eligible for the expiration sweep, if it expires at all."
2167
+ },
2168
+ {
2169
+ name: "lat",
2170
+ longName: "locksAt",
2171
+ converter: "optionalFirestoreDate()",
2172
+ tsType: "Maybe<Date>",
2173
+ optional: true,
2174
+ description: "The date reopening stops being possible \u2014 the instant the submission becomes FULLY LOCKED."
2175
+ },
2176
+ {
2177
+ name: "lby",
2178
+ longName: "lockedBy",
2179
+ converter: "optionalFirestoreUID()",
2180
+ tsType: "Maybe<FirebaseAuthUserId>",
2181
+ optional: true,
2182
+ description: "The user who locked the submission early, when a caller did rather than the deadline passing."
2183
+ },
2184
+ {
2185
+ name: "rc",
2186
+ longName: "reopenCount",
2187
+ converter: "firestoreNumber({ default: 0 })",
2188
+ tsType: "number",
2189
+ optional: false,
2190
+ description: "Monotonic count of times this space has been REOPENED after a submission."
2191
+ },
2192
+ {
2193
+ name: "rat",
2194
+ longName: "reopenedAt",
2195
+ converter: "optionalFirestoreDate()",
2196
+ tsType: "Maybe<Date>",
2197
+ optional: true,
2198
+ description: "The date the space was last reopened, if it ever was."
2199
+ },
2200
+ {
2201
+ name: "rby",
2202
+ longName: "reopenedBy",
2203
+ converter: "optionalFirestoreUID()",
2204
+ tsType: "Maybe<FirebaseAuthUserId>",
2205
+ optional: true,
2206
+ description: "The user who last reopened the space."
2159
2207
  }
2160
2208
  ],
2161
2209
  enums: [
@@ -2212,7 +2260,7 @@ var FIREBASE_MODELS = [
2212
2260
  description: "Processing state of a submitted {@link FormSpace}."
2213
2261
  }
2214
2262
  ],
2215
- detectionHints: ["t", "n", "ps", "m", "uc", "f", "pn", "pat", "uat", "sat", "cpat", "eat"],
2263
+ detectionHints: ["t", "n", "ps", "m", "uc", "f", "pn", "pat", "uat", "sat", "fsat", "cpat", "eat", "lat", "lby", "rc", "rat", "rby"],
2216
2264
  description: "A type-registered container for a client-side form: its in-progress JSON, its uploads, and its submission state.",
2217
2265
  modelGroup: "FormSpace",
2218
2266
  collectionKind: "root",
@@ -10086,7 +10134,7 @@ function collectFromTypeReference(ref, context) {
10086
10134
  result = collectFromTypeNode(args[0], { ...context, forceOptional: false }).map((p) => ({ ...p, required: true }));
10087
10135
  } else if (refName === "Omit" && args.length === 2) {
10088
10136
  const keys = parseKeyUnion(args[1]);
10089
- const merged = /* @__PURE__ */ new Set([...context.omit, ...keys]);
10137
+ const merged = /* @__PURE__ */ new Set([...Array.from(context.omit), ...Array.from(keys)]);
10090
10138
  result = collectFromTypeNode(args[0], { ...context, omit: merged });
10091
10139
  } else if (refName === "Pick" && args.length === 2) {
10092
10140
  const keys = parseKeyUnion(args[1]);
@@ -10121,6 +10169,7 @@ var PropertyAccumulator = class {
10121
10169
  constructor(context) {
10122
10170
  this.context = context;
10123
10171
  }
10172
+ context;
10124
10173
  entries = [];
10125
10174
  index = /* @__PURE__ */ new Map();
10126
10175
  add(entry) {
@@ -11427,7 +11476,7 @@ function tryTransitiveResolution(input) {
11427
11476
  const calleeLine = resolved.decl.getStartLineNumber();
11428
11477
  warnings.push({ kind: "unannotated-query-helper", severity: "warning", name: factoryName, callee: calleeName, calleeFilePath, calleeLine, filePath, line: call.getStartLineNumber() });
11429
11478
  }
11430
- const nextVisited = /* @__PURE__ */ new Set([...visited, calleeKey]);
11479
+ const nextVisited = new Set(visited).add(calleeKey);
11431
11480
  walkBodyInto({
11432
11481
  decl: resolved.decl,
11433
11482
  factoryName,
@@ -15407,13 +15456,21 @@ function formatBuildFailure(outcome) {
15407
15456
  }
15408
15457
  function formatWriteSummary(input) {
15409
15458
  const { outputPath, diff, generatedComposites, generatedFieldOverrides } = input;
15410
- const lines = [`generate-firestore-indexes: wrote ${outputPath}`, ` composites: ${generatedComposites} (added ${diff.added.length}, removed ${diff.removed.length}, unchanged ${diff.unchanged.length})`, ` fieldOverrides: ${generatedFieldOverrides} (added ${diff.fieldOverridesAdded.length}, removed ${diff.fieldOverridesRemoved.length}, unchanged ${diff.fieldOverridesUnchanged.length})`];
15459
+ const lines = [
15460
+ `generate-firestore-indexes: wrote ${outputPath}`,
15461
+ ` composites: ${generatedComposites} (added ${diff.added.length}, removed ${diff.removed.length}, unchanged ${diff.unchanged.length})`,
15462
+ ` fieldOverrides: ${generatedFieldOverrides} (added ${diff.fieldOverridesAdded.length}, removed ${diff.fieldOverridesRemoved.length}, unchanged ${diff.fieldOverridesUnchanged.length})`
15463
+ ];
15411
15464
  return appendDiffSection(lines, diff).join("\n");
15412
15465
  }
15413
15466
  function formatCheckSummary(input) {
15414
15467
  const { drift, outputPath, diff, generatedComposites, generatedFieldOverrides } = input;
15415
15468
  const headline = drift ? `generate-firestore-indexes: drift detected against ${outputPath}` : `generate-firestore-indexes: in sync (${outputPath})`;
15416
- const lines = [headline, ` composites: ${generatedComposites} (added ${diff.added.length}, removed ${diff.removed.length}, unchanged ${diff.unchanged.length})`, ` fieldOverrides: ${generatedFieldOverrides} (added ${diff.fieldOverridesAdded.length}, removed ${diff.fieldOverridesRemoved.length}, unchanged ${diff.fieldOverridesUnchanged.length})`];
15469
+ const lines = [
15470
+ headline,
15471
+ ` composites: ${generatedComposites} (added ${diff.added.length}, removed ${diff.removed.length}, unchanged ${diff.unchanged.length})`,
15472
+ ` fieldOverrides: ${generatedFieldOverrides} (added ${diff.fieldOverridesAdded.length}, removed ${diff.fieldOverridesRemoved.length}, unchanged ${diff.fieldOverridesUnchanged.length})`
15473
+ ];
15417
15474
  return appendDiffSection(lines, diff).join("\n");
15418
15475
  }
15419
15476
  function appendDiffSection(lines, diff) {
@@ -19767,7 +19824,7 @@ function registerResources(server, options = {}) {
19767
19824
  if (options.authRegistry !== void 0) {
19768
19825
  registerAuthResource(server, { registry: options.authRegistry });
19769
19826
  }
19770
- registerFirestoreRulesResource(server, { ...options.cwd === void 0 ? {} : { cwd: options.cwd } });
19827
+ registerFirestoreRulesResource(server, options.cwd === void 0 ? {} : { cwd: options.cwd });
19771
19828
  }
19772
19829
 
19773
19830
  // packages/dbx-components-mcp/src/tools/lookup-form.tool.ts
@@ -19860,7 +19917,19 @@ function formatBrief(field) {
19860
19917
  const tierBits = formatTierBits(field);
19861
19918
  const arrayOptional = field.arrayOutput === "optional" ? " (single or array)" : "";
19862
19919
  const array = field.arrayOutput === "yes" ? " (array)" : arrayOptional;
19863
- return [`## ${field.factoryName}`, "", `**slug:** \`${field.slug}\` \xB7 **tier:** \`${field.tier}\` \xB7 **produces:** \`${field.produces}\`${array}${tierBits}`, "", field.description, "", "```ts", field.minimalExample, "```", "", `\u2192 Call \`dbx_form_lookup topic="${field.slug}" depth="full"\` for the config table and full example.`].join("\n");
19920
+ return [
19921
+ `## ${field.factoryName}`,
19922
+ "",
19923
+ `**slug:** \`${field.slug}\` \xB7 **tier:** \`${field.tier}\` \xB7 **produces:** \`${field.produces}\`${array}${tierBits}`,
19924
+ "",
19925
+ field.description,
19926
+ "",
19927
+ "```ts",
19928
+ field.minimalExample,
19929
+ "```",
19930
+ "",
19931
+ `\u2192 Call \`dbx_form_lookup topic="${field.slug}" depth="full"\` for the config table and full example.`
19932
+ ].join("\n");
19864
19933
  }
19865
19934
  function formatFull(field) {
19866
19935
  const header = formatHeader(field);
@@ -20362,7 +20431,19 @@ function formatSearchResults(input) {
20362
20431
  const field = hit.entry;
20363
20432
  const optionalArray = field.arrayOutput === "optional" ? " *(single or array)*" : "";
20364
20433
  const array = field.arrayOutput === "yes" ? " *(array)*" : optionalArray;
20365
- lines.push(`## \`${field.slug}\` \xB7 form \xB7 score ${hit.score}`, "", `- **factory:** \`${field.factoryName}\``, `- **tier:** \`${field.tier}\``, `- **produces:** \`${field.produces}\`${array}`, `- **matched:** \`${hit.matchedTokens.join(", ")}\``, "", field.description, "", `\u2192 \`dbx_form_lookup topic="${field.slug}"\` for full docs.`, "");
20434
+ lines.push(
20435
+ `## \`${field.slug}\` \xB7 form \xB7 score ${hit.score}`,
20436
+ "",
20437
+ `- **factory:** \`${field.factoryName}\``,
20438
+ `- **tier:** \`${field.tier}\``,
20439
+ `- **produces:** \`${field.produces}\`${array}`,
20440
+ `- **matched:** \`${hit.matchedTokens.join(", ")}\``,
20441
+ "",
20442
+ field.description,
20443
+ "",
20444
+ `\u2192 \`dbx_form_lookup topic="${field.slug}"\` for full docs.`,
20445
+ ""
20446
+ );
20366
20447
  }
20367
20448
  return lines.join("\n").trimEnd();
20368
20449
  }
@@ -21414,7 +21495,19 @@ function formatSearchResults2(options) {
21414
21495
  } else {
21415
21496
  const lines = [`# Search: \`${query}\``, "", `Tokens: \`${tokenDisplay}\`${scopeLabel} \xB7 ${hits.length} result${hits.length === 1 ? "" : "s"}`, ""];
21416
21497
  for (const hit of hits) {
21417
- lines.push(`## \`${hit.entry.slug}\` \xB7 ${hit.entry.category} \xB7 score ${hit.score}`, "", `- **class:** \`${hit.entry.className}\``, `- **kind:** \`${hit.entry.kind}\``, `- **selector:** \`${hit.entry.selector}\``, `- **matched:** \`${hit.matchedTokens.join(", ")}\``, "", hit.entry.description, "", `\u2192 \`dbx_ui_lookup topic="${hit.entry.slug}"\` for full docs.`, "");
21498
+ lines.push(
21499
+ `## \`${hit.entry.slug}\` \xB7 ${hit.entry.category} \xB7 score ${hit.score}`,
21500
+ "",
21501
+ `- **class:** \`${hit.entry.className}\``,
21502
+ `- **kind:** \`${hit.entry.kind}\``,
21503
+ `- **selector:** \`${hit.entry.selector}\``,
21504
+ `- **matched:** \`${hit.matchedTokens.join(", ")}\``,
21505
+ "",
21506
+ hit.entry.description,
21507
+ "",
21508
+ `\u2192 \`dbx_ui_lookup topic="${hit.entry.slug}"\` for full docs.`,
21509
+ ""
21510
+ );
21418
21511
  }
21419
21512
  appendRelatedExamples(lines, hits, examplesRegistry);
21420
21513
  result = lines.join("\n").trimEnd();
@@ -21510,7 +21603,6 @@ import { DbxActionConfirmConfig } from '@dereekb/dbx-web';
21510
21603
 
21511
21604
  @Component({
21512
21605
  selector: 'app-danger-zone',
21513
- standalone: true,
21514
21606
  imports: [DbxCardBoxComponent, DbxBarDirective, DbxButtonComponent, DbxActionDirective, DbxActionConfirmDirective],
21515
21607
  template: \`
21516
21608
  <dbx-card-box header="Danger zone" icon="warning">
@@ -21527,8 +21619,7 @@ import { DbxActionConfirmConfig } from '@dereekb/dbx-web';
21527
21619
  </dbx-button>
21528
21620
  </dbx-bar>
21529
21621
  </dbx-card-box>
21530
- \`,
21531
- changeDetection: ChangeDetectionStrategy.OnPush
21622
+ \`
21532
21623
  })
21533
21624
  export class DangerZoneComponent {
21534
21625
  readonly deleteAccountAction = /* DbxActionContextStoreSourceInstance<...> */ null;
@@ -21579,7 +21670,6 @@ interface Item {
21579
21670
 
21580
21671
  @Component({
21581
21672
  selector: 'app-items-page',
21582
- standalone: true,
21583
21673
  imports: [DbxSectionPageComponent, DbxListComponent, DbxListEmptyContentComponent, DbxPagebarComponent, DbxButtonComponent, DbxActionDirective],
21584
21674
  template: \`
21585
21675
  <dbx-section-page header="Items" icon="list" scroll="body">
@@ -21594,8 +21684,7 @@ interface Item {
21594
21684
  </dbx-list-empty-content>
21595
21685
  </dbx-list>
21596
21686
  </dbx-section-page>
21597
- \`,
21598
- changeDetection: ChangeDetectionStrategy.OnPush
21687
+ \`
21599
21688
  })
21600
21689
  export class ItemsPageComponent {
21601
21690
  readonly items$: Observable<ListLoadingState<Item>> = /* ... */ null!;
@@ -21641,7 +21730,6 @@ interface Item {
21641
21730
 
21642
21731
  @Component({
21643
21732
  selector: 'app-items-loaded',
21644
- standalone: true,
21645
21733
  imports: [CommonModule, DbxLoadingComponent, DbxListComponent, DbxListEmptyContentComponent, DbxIntroActionSectionComponent],
21646
21734
  template: \`
21647
21735
  <dbx-loading [state]="state$ | async" text="Loading items...">
@@ -21657,8 +21745,7 @@ interface Item {
21657
21745
  </dbx-list>
21658
21746
  </dbx-intro-action-section>
21659
21747
  </dbx-loading>
21660
- \`,
21661
- changeDetection: ChangeDetectionStrategy.OnPush
21748
+ \`
21662
21749
  })
21663
21750
  export class ItemsLoadedComponent {
21664
21751
  readonly state$: Observable<ListLoadingState<Item>> = /* ... */ null!;
@@ -21707,7 +21794,6 @@ import { MatInputModule } from '@angular/material/input';
21707
21794
 
21708
21795
  @Component({
21709
21796
  selector: 'app-account-settings',
21710
- standalone: true,
21711
21797
  imports: [DbxSectionComponent, DbxBarDirective, DbxButtonComponent, DbxButtonSpacerDirective, DbxActionDirective, ReactiveFormsModule, MatFormFieldModule, MatInputModule],
21712
21798
  template: \`
21713
21799
  <dbx-section header="Account" icon="person" hint="Update your profile">
@@ -21724,8 +21810,7 @@ import { MatInputModule } from '@angular/material/input';
21724
21810
  <dbx-button text="Cancel" stroked (btnClick)="cancel()"></dbx-button>
21725
21811
  </dbx-bar>
21726
21812
  </dbx-section>
21727
- \`,
21728
- changeDetection: ChangeDetectionStrategy.OnPush
21813
+ \`
21729
21814
  })
21730
21815
  export class AccountSettingsComponent {
21731
21816
  private readonly fb = inject(FormBuilder);
@@ -21762,7 +21847,6 @@ import { UIRouterModule } from '@uirouter/angular';
21762
21847
 
21763
21848
  @Component({
21764
21849
  selector: 'app-shell',
21765
- standalone: true,
21766
21850
  imports: [DbxSidenavPageComponent, DbxSidenavComponent, DbxContentPageDirective, UIRouterModule],
21767
21851
  template: \`
21768
21852
  <dbx-sidenav-page>
@@ -21771,8 +21855,7 @@ import { UIRouterModule } from '@uirouter/angular';
21771
21855
  <ui-view></ui-view>
21772
21856
  </div>
21773
21857
  </dbx-sidenav-page>
21774
- \`,
21775
- changeDetection: ChangeDetectionStrategy.OnPush
21858
+ \`
21776
21859
  })
21777
21860
  export class AppShellComponent {
21778
21861
  readonly navAnchors: ClickableAnchor[] = [
@@ -21823,7 +21906,6 @@ interface Member {
21823
21906
 
21824
21907
  @Component({
21825
21908
  selector: 'app-members-page',
21826
- standalone: true,
21827
21909
  imports: [CommonModule, MatButtonModule, MatIconModule, DbxSectionPageComponent, DbxTwoColumnComponent, DbxTwoColumnRightComponent, DbxListComponent],
21828
21910
  template: \`
21829
21911
  <dbx-section-page header="Members" icon="group" scroll="body">
@@ -21835,8 +21917,7 @@ interface Member {
21835
21917
  </dbx-two-column-right>
21836
21918
  </dbx-two-column>
21837
21919
  </dbx-section-page>
21838
- \`,
21839
- changeDetection: ChangeDetectionStrategy.OnPush
21920
+ \`
21840
21921
  })
21841
21922
  export class MembersPageComponent {
21842
21923
  readonly members$: Observable<ListLoadingState<Member>> = /* ... */ null!;
@@ -25365,6 +25446,96 @@ var RULE_CATALOG = [
25365
25446
  whenItApplies: "When a key on `<app>SystemStateStoredDataConverterMap` is neither a local `<NAME>_SYSTEM_STATE_TYPE` constant nor an imported identifier.",
25366
25447
  whenItDoesNotApply: "Keys imported from upstream packages \u2014 those are detected via `importedIdentifiers` and skipped.",
25367
25448
  canonicalFix: "Either declare the missing constant locally or import it from the upstream package that owns it."
25449
+ },
25450
+ {
25451
+ code: "WORKSPACE_DEPLOY_ENVIRONMENT_FILE_MISSING",
25452
+ source: "dbx_workspace_validate",
25453
+ severity: "error",
25454
+ title: "A configuration's file-replacement or esbuild-config path does not exist on disk",
25455
+ whatItFlags: "A configuration's file-replacement or esbuild-config path does not exist on disk.",
25456
+ whenItApplies: "When a `fileReplacements` `replace` / `with` path, or a configuration's `esbuildConfig` path, names a file that is absent from the working tree.",
25457
+ whenItDoesNotApply: "Generated files produced by an earlier target in the same pipeline \u2014 declare them as that target's `outputs` so the dependency is explicit, or the reference is genuinely broken.",
25458
+ canonicalFix: "Create the missing file or correct the path. An Angular build fails loudly on a missing replacement, but an esbuild `additionalProperties: true` schema accepts a bad path silently."
25459
+ },
25460
+ {
25461
+ code: "WORKSPACE_DEPLOY_ENVIRONMENT_FILE_UNREFERENCED",
25462
+ source: "dbx_workspace_validate",
25463
+ severity: "warning",
25464
+ title: "An `environment.<name>.ts` file exists but no build configuration references it",
25465
+ whatItFlags: "An `environment.<name>.ts` file exists but no build configuration references it.",
25466
+ whenItApplies: "When a file matching `environment.*.ts` sits in a project's `src/environments/` directory and appears in no configuration's `fileReplacements`, and no `esbuildConfig` file references it.",
25467
+ whenItDoesNotApply: "The base `environment.ts` (always the replacement source, never a target), and environment files imported directly by application code rather than swapped in by the build.",
25468
+ canonicalFix: "Add the configuration that consumes it, or delete the file. An unreachable environment file reads as a working deploy lane that does not exist \u2014 the file is edited, reviewed, and never compiled."
25469
+ },
25470
+ {
25471
+ code: "WORKSPACE_DEPLOY_HOSTING_TARGET_MISSING",
25472
+ source: "dbx_workspace_validate",
25473
+ severity: "error",
25474
+ title: "A deploy command targets a firebase hosting target that `firebase.json` does not declare",
25475
+ whatItFlags: "A deploy command targets a firebase hosting target that `firebase.json` does not declare.",
25476
+ whenItApplies: "When a deploy command runs `firebase deploy --only hosting:<target>` and `<target>` matches no `target` (or `site`) entry under `hosting` in `firebase.json`.",
25477
+ whenItDoesNotApply: "Workspaces that resolve hosting targets through `.firebaserc` target aliases the scanner does not read \u2014 verify manually if this misfires.",
25478
+ canonicalFix: "Add the hosting entry to `firebase.json`, or point the command at a declared target. `firebase deploy` fails on an unknown hosting target, so this breaks the lane outright."
25479
+ },
25480
+ {
25481
+ code: "WORKSPACE_DEPLOY_LANE_ASYMMETRIC",
25482
+ source: "dbx_workspace_validate",
25483
+ severity: "warning",
25484
+ title: "A deploy lane is wired on one side of the app/api pair but not the other",
25485
+ whatItFlags: "A deploy lane is wired on one side of the app/api pair but not the other.",
25486
+ whenItApplies: "When one project in the workspace declares `ci-deploy-<lane>` and a sibling project that declares at least one other `ci-deploy-*` lane does not declare this one.",
25487
+ whenItDoesNotApply: "Projects deliberately deployed on their own cadence, and workspaces with a single deployable project. Suppress by giving the sibling the lane, or accept the warning.",
25488
+ canonicalFix: "Add the missing `ci-deploy-<lane>` target to the sibling project. A lane present on the API but not the web app (or vice versa) deploys half an application and leaves the two halves on different builds."
25489
+ },
25490
+ {
25491
+ code: "WORKSPACE_DEPLOY_LANE_NO_ENVIRONMENT_SELECTION",
25492
+ source: "dbx_workspace_validate",
25493
+ severity: "error",
25494
+ title: "A deploy lane builds a configuration that selects no environment file",
25495
+ whatItFlags: "A deploy lane builds a configuration that selects no environment file.",
25496
+ whenItApplies: "When a `ci-deploy-<lane>` target builds a configuration that provides no environment-selection mechanism for its executor \u2014 no `fileReplacements` for an Angular `application` build, or no lane-specific `esbuildConfig` for an `@nx/esbuild:esbuild` build.",
25497
+ whenItDoesNotApply: "Non-deploy configurations such as `development`, which are expected to compile the base `environment.ts`. Only configurations reachable from a `ci-deploy-*` target are checked.",
25498
+ canonicalFix: "Add the `fileReplacements` entry (or the lane's `esbuildConfig`) to the configuration. Without it the lane deploys the unreplaced `environment.ts` \u2014 localhost URLs, dev credentials, and any developer-only function map \u2014 to a live project."
25499
+ },
25500
+ {
25501
+ code: "WORKSPACE_TARGET_OUTPUT_ESCAPES_ROOT",
25502
+ source: "dbx_workspace_validate",
25503
+ severity: "error",
25504
+ title: "A target's `outputs` entry resolves outside the workspace root",
25505
+ whatItFlags: "A target's `outputs` entry resolves outside the workspace root.",
25506
+ whenItApplies: 'When an `outputs` entry \u2014 after `{options.<name>}` substitution from the target\'s own options \u2014 resolves to a path above the workspace root. The common shape is `outputs: ["{options.reportsDirectory}"]` with a `../../`-relative option value.',
25507
+ whenItDoesNotApply: "Entries anchored by `{workspaceRoot}` or `{projectRoot}`, and entries whose `{options.<name>}` token has no resolvable value (skipped rather than guessed).",
25508
+ canonicalFix: 'Anchor the entry explicitly, e.g. `"{workspaceRoot}/coverage/{projectRoot}"`. Nx refuses to cache an output it cannot place inside the workspace and will report the task as flaky instead of failing loudly.'
25509
+ },
25510
+ {
25511
+ code: "WORKSPACE_TARGET_REF_CONFIGURATION_MISSING",
25512
+ source: "dbx_workspace_validate",
25513
+ severity: "error",
25514
+ title: "An `nx run <project>:<target>:<configuration>` reference names a configuration the target does not declare",
25515
+ whatItFlags: "An `nx run <project>:<target>:<configuration>` reference names a configuration the target does not declare.",
25516
+ whenItApplies: "Whenever a reference carries a configuration segment and the target's `configurations` map has no such key.",
25517
+ whenItDoesNotApply: "Targets that declare no `configurations` at all \u2014 Nx tolerates a configuration argument there, so it is not reported.",
25518
+ canonicalFix: "Rename the reference to a declared configuration (a `prod` / `production` mismatch is the usual cause), or add the configuration to the target. Nx does NOT error on an unknown configuration: `create-task-graph.js` silently substitutes `defaultConfiguration`, so a mismatch here ships whatever that default builds \u2014 and ships the base options with no configuration merged at all when no default is set."
25519
+ },
25520
+ {
25521
+ code: "WORKSPACE_TARGET_REF_PROJECT_MISSING",
25522
+ source: "dbx_workspace_validate",
25523
+ severity: "error",
25524
+ title: "An `nx run <project>:...` reference names a project that does not exist in the workspace",
25525
+ whatItFlags: "An `nx run <project>:...` reference names a project that does not exist in the workspace.",
25526
+ whenItApplies: "When a target reference in a `project.json` command, CI config, shell script, or npm script names a project with no matching entry in the workspace project graph.",
25527
+ whenItDoesNotApply: "References built from shell variables or CI interpolation the scanner cannot resolve \u2014 those are skipped rather than reported.",
25528
+ canonicalFix: "Create the project, or update the reference to the project's real name. A renamed or deleted project leaves these references behind and they only fail at deploy time."
25529
+ },
25530
+ {
25531
+ code: "WORKSPACE_TARGET_REF_TARGET_MISSING",
25532
+ source: "dbx_workspace_validate",
25533
+ severity: "error",
25534
+ title: "An `nx run <project>:<target>` reference names a target the project does not declare",
25535
+ whatItFlags: "An `nx run <project>:<target>` reference names a target the project does not declare.",
25536
+ whenItApplies: "When the referenced project exists but has no target by that name (after `nx.json` `targetDefaults` are accounted for).",
25537
+ whenItDoesNotApply: "Targets contributed by an inferred-target plugin the scanner does not evaluate \u2014 declare the plugin's targets explicitly if this misfires.",
25538
+ canonicalFix: "Add the target to the project, or point the reference at an existing one. This is the single most common way a CI lane silently stops doing anything."
25368
25539
  }
25369
25540
  ];
25370
25541
 
@@ -25426,7 +25597,31 @@ function attachRemediation(code17) {
25426
25597
 
25427
25598
  // packages/dbx-cli/validate/src/lib/_core/rule-catalog/format.ts
25428
25599
  function formatRuleAsMarkdown(entry) {
25429
- const lines = [`# \`${entry.code}\``, "", `**${entry.title}**`, "", `- **Severity:** \`${entry.severity}\``, `- **Source tool:** \`${entry.source}\``, "", "## What it flags", "", entry.whatItFlags, "", "## When it applies", "", entry.whenItApplies, "", "## When it does NOT apply", "", entry.whenItDoesNotApply, "", "## Canonical fix", "", entry.canonicalFix, ""];
25600
+ const lines = [
25601
+ `# \`${entry.code}\``,
25602
+ "",
25603
+ `**${entry.title}**`,
25604
+ "",
25605
+ `- **Severity:** \`${entry.severity}\``,
25606
+ `- **Source tool:** \`${entry.source}\``,
25607
+ "",
25608
+ "## What it flags",
25609
+ "",
25610
+ entry.whatItFlags,
25611
+ "",
25612
+ "## When it applies",
25613
+ "",
25614
+ entry.whenItApplies,
25615
+ "",
25616
+ "## When it does NOT apply",
25617
+ "",
25618
+ entry.whenItDoesNotApply,
25619
+ "",
25620
+ "## Canonical fix",
25621
+ "",
25622
+ entry.canonicalFix,
25623
+ ""
25624
+ ];
25430
25625
  if (entry.canonicalFixTemplate) {
25431
25626
  lines.push("## Fix template", "", entry.canonicalFixTemplate, "");
25432
25627
  }
@@ -35767,7 +35962,15 @@ function appendGroupSection(lines, apiRel, group) {
35767
35962
  function appendFileTable(lines, group) {
35768
35963
  lines.push("", "| File | Kind | Subgroups | Canonical | Rename suggestion |", "|---|---|---|---|---|");
35769
35964
  for (const file of group.files) {
35770
- lines.push("| " + [code3(file.filename), KIND_LABEL[file.classification.kind], file.classification.subgroups.length === 0 ? "\u2014" : code3(file.classification.subgroups.join(".")), file.classification.isCanonical ? "\u2713" : "\u2717", file.classification.recommendedRename === void 0 ? "\u2014" : code3(file.classification.recommendedRename)].join(" | ") + " |");
35965
+ lines.push(
35966
+ "| " + [
35967
+ code3(file.filename),
35968
+ KIND_LABEL[file.classification.kind],
35969
+ file.classification.subgroups.length === 0 ? "\u2014" : code3(file.classification.subgroups.join(".")),
35970
+ file.classification.isCanonical ? "\u2713" : "\u2717",
35971
+ file.classification.recommendedRename === void 0 ? "\u2014" : code3(file.classification.recommendedRename)
35972
+ ].join(" | ") + " |"
35973
+ );
35771
35974
  }
35772
35975
  }
35773
35976
  function appendWhereToAddSection(lines, apiRel, group) {
@@ -35783,7 +35986,17 @@ function appendRecommendation(lines, rec, group) {
35783
35986
  lines.push("", `- **${rec.label}** \u2192 \`${rec.canonicalPath}\` ${status}`, ` ${rec.summary}`);
35784
35987
  }
35785
35988
  function appendConventionLegend(lines) {
35786
- lines.push("", "## Naming convention", "", "- `<group>.crud.spec.ts` \u2014 non-scenario CRUD tests.", "- `<group>.crud.<sub>[.<sub>...].spec.ts` \u2014 focused CRUD sub-test.", "- `<group>.scenario.spec.ts` \u2014 generic multi-step scenario tests.", "- `<group>.scenario.<sub>[.<sub>...].spec.ts` \u2014 focused scenario sub-bucket.", "", "Drift forms are still parsed but flagged \u2014 see the `Rename suggestion` column.");
35989
+ lines.push(
35990
+ "",
35991
+ "## Naming convention",
35992
+ "",
35993
+ "- `<group>.crud.spec.ts` \u2014 non-scenario CRUD tests.",
35994
+ "- `<group>.crud.<sub>[.<sub>...].spec.ts` \u2014 focused CRUD sub-test.",
35995
+ "- `<group>.scenario.spec.ts` \u2014 generic multi-step scenario tests.",
35996
+ "- `<group>.scenario.<sub>[.<sub>...].spec.ts` \u2014 focused scenario sub-bucket.",
35997
+ "",
35998
+ "Drift forms are still parsed but flagged \u2014 see the `Rename suggestion` column."
35999
+ );
35787
36000
  }
35788
36001
  function appendDriftSummary(lines, catalog) {
35789
36002
  const driftFiles = [];
@@ -35929,7 +36142,14 @@ function stringify(value) {
35929
36142
 
35930
36143
  // packages/dbx-cli/model-test/src/lib/fixture/format.markdown.ts
35931
36144
  function formatListAsMarkdown(extraction) {
35932
- const lines = [`# App fixtures \u2014 ${extraction.fixturePath}`, "", `Detected workspace prefix: \`${extraction.prefix ?? "(none)"}\``, `Identity imports: ${extraction.identityImports.length === 0 ? "_None._" : extraction.identityImports.map((s) => "`" + s + "`").join(", ")}`, "", `## Fixtures (${extraction.entries.length})`];
36145
+ const lines = [
36146
+ `# App fixtures \u2014 ${extraction.fixturePath}`,
36147
+ "",
36148
+ `Detected workspace prefix: \`${extraction.prefix ?? "(none)"}\``,
36149
+ `Identity imports: ${extraction.identityImports.length === 0 ? "_None._" : extraction.identityImports.map((s) => "`" + s + "`").join(", ")}`,
36150
+ "",
36151
+ `## Fixtures (${extraction.entries.length})`
36152
+ ];
35933
36153
  if (extraction.entries.length === 0) {
35934
36154
  lines.push("", "_No model fixtures found._");
35935
36155
  } else {
@@ -35986,7 +36206,15 @@ function nonModelDescription(entry) {
35986
36206
  return "Non-Firestore-model fixture.";
35987
36207
  }
35988
36208
  function appendLookupTriplet(lines, entry) {
35989
- lines.push("## Triplet", "", `- Fixture: \`${entry.fixtureClassName}\` (lines ${entry.fixtureLine}-${entry.fixtureEndLine})`, `- Instance: \`${entry.instanceClassName}\` (lines ${entry.instanceLine}-${entry.instanceEndLine})`, `- Params: \`${entry.paramsTypeName}\``, `- Factory: \`${entry.factoryName ?? "\u2014"}\``, `- Singleton: \`${entry.singletonName ?? "\u2014"}\``);
36209
+ lines.push(
36210
+ "## Triplet",
36211
+ "",
36212
+ `- Fixture: \`${entry.fixtureClassName}\` (lines ${entry.fixtureLine}-${entry.fixtureEndLine})`,
36213
+ `- Instance: \`${entry.instanceClassName}\` (lines ${entry.instanceLine}-${entry.instanceEndLine})`,
36214
+ `- Params: \`${entry.paramsTypeName}\``,
36215
+ `- Factory: \`${entry.factoryName ?? "\u2014"}\``,
36216
+ `- Singleton: \`${entry.singletonName ?? "\u2014"}\``
36217
+ );
35990
36218
  }
35991
36219
  function appendLookupGenerics(lines, entry) {
35992
36220
  if (entry.fixtureExtendsGenerics.length > 0) {
@@ -36156,11 +36384,19 @@ function renderParamsBlock(input, paramsTypeName, todos) {
36156
36384
  }
36157
36385
  function renderInstanceBlock(input) {
36158
36386
  input.todos.push(`Add instance methods to \`${input.instanceClassName}\`.`);
36159
- return [`export class ${input.instanceClassName}<F extends FirebaseAdminFunctionTestContextInstance = FirebaseAdminFunctionTestContextInstance> extends ModelTestContextInstance<${input.model}, ${input.modelDocumentTypeName}, ${input.prefix}FunctionContextFixtureInstance<F>> {`, " // TODO: implement instance-side methods (these own the real logic).", "}"].join("\n");
36387
+ return [
36388
+ `export class ${input.instanceClassName}<F extends FirebaseAdminFunctionTestContextInstance = FirebaseAdminFunctionTestContextInstance> extends ModelTestContextInstance<${input.model}, ${input.modelDocumentTypeName}, ${input.prefix}FunctionContextFixtureInstance<F>> {`,
36389
+ " // TODO: implement instance-side methods (these own the real logic).",
36390
+ "}"
36391
+ ].join("\n");
36160
36392
  }
36161
36393
  function renderFixtureBlock(input) {
36162
36394
  input.todos.push(`Add forwarder methods to \`${input.fixtureClassName}\` after defining instance methods.`);
36163
- return [`export class ${input.fixtureClassName}<F extends FirebaseAdminFunctionTestContextInstance = FirebaseAdminFunctionTestContextInstance> extends ModelTestContextFixture<${input.model}, ${input.modelDocumentTypeName}, ${input.prefix}FunctionContextFixtureInstance<F>, ${input.prefix}FunctionContextFixture<F>, ${input.instanceClassName}<F>> {`, " // TODO: forward instance methods. Run `dbx_model_fixture_forward` after adding instance methods.", "}"].join("\n");
36395
+ return [
36396
+ `export class ${input.fixtureClassName}<F extends FirebaseAdminFunctionTestContextInstance = FirebaseAdminFunctionTestContextInstance> extends ModelTestContextFixture<${input.model}, ${input.modelDocumentTypeName}, ${input.prefix}FunctionContextFixtureInstance<F>, ${input.prefix}FunctionContextFixture<F>, ${input.instanceClassName}<F>> {`,
36397
+ " // TODO: forward instance methods. Run `dbx_model_fixture_forward` after adding instance methods.",
36398
+ "}"
36399
+ ].join("\n");
36164
36400
  }
36165
36401
  function renderFactoryBlock(input) {
36166
36402
  const generics = renderFactoryGenerics(input);
@@ -36180,7 +36416,15 @@ function renderFactoryBlock(input) {
36180
36416
  return lines.join("\n");
36181
36417
  }
36182
36418
  function renderFactoryGenerics(input) {
36183
- const baseGenerics = [input.model, input.modelDocumentTypeName, input.paramsTypeName, `${input.prefix}FunctionContextFixtureInstance<FirebaseAdminFunctionTestContextInstance>`, `${input.prefix}FunctionContextFixture<FirebaseAdminFunctionTestContextInstance>`, `${input.instanceClassName}<FirebaseAdminFunctionTestContextInstance>`, `${input.fixtureClassName}<FirebaseAdminFunctionTestContextInstance>`];
36419
+ const baseGenerics = [
36420
+ input.model,
36421
+ input.modelDocumentTypeName,
36422
+ input.paramsTypeName,
36423
+ `${input.prefix}FunctionContextFixtureInstance<FirebaseAdminFunctionTestContextInstance>`,
36424
+ `${input.prefix}FunctionContextFixture<FirebaseAdminFunctionTestContextInstance>`,
36425
+ `${input.instanceClassName}<FirebaseAdminFunctionTestContextInstance>`,
36426
+ `${input.fixtureClassName}<FirebaseAdminFunctionTestContextInstance>`
36427
+ ];
36184
36428
  if (input.collectionGenericArg) {
36185
36429
  return [...baseGenerics, input.collectionGenericArg];
36186
36430
  }
@@ -36202,10 +36446,13 @@ function renderFactoryCallbacks(input) {
36202
36446
  } else {
36203
36447
  callbacks.push(`getCollection: (fi) => fi.${defaultCollectionsAccessor(input.prefix)}.${camelInitial(input.model)}Collection`);
36204
36448
  }
36205
- callbacks.push(`makeInstance: (delegate, ref, testInstance) => new ${input.instanceClassName}(delegate, ref, testInstance)`, `makeRef: async (collection, params, _p) => {
36449
+ callbacks.push(
36450
+ `makeInstance: (delegate, ref, testInstance) => new ${input.instanceClassName}(delegate, ref, testInstance)`,
36451
+ `makeRef: async (collection, params, _p) => {
36206
36452
  // TODO: choose the correct ref strategy (auto id vs deterministic id).
36207
36453
  return collection.documentAccessor().newDocument().documentRef;
36208
- }`);
36454
+ }`
36455
+ );
36209
36456
  if (input.withInitDocument) {
36210
36457
  callbacks.push(`initDocument: async (instance, params) => {
36211
36458
  // TODO: seed the new document with sensible defaults from \`params\`.
@@ -37405,7 +37652,18 @@ function formatReportAsJson4(report) {
37405
37652
  // packages/dbx-cli/validate/src/lib/dbx-asset-list-app/format.markdown.ts
37406
37653
  function formatReportAsMarkdown4(report) {
37407
37654
  const basename12 = report.componentDir.split("/").pop() ?? report.componentDir;
37408
- const lines = [`# App assets \u2014 ${basename12}`, "", `Component: \`${report.componentDir}\``, `App: \`${report.apiDir}\``, "", `\`src/lib/assets.ts\`: ${formatBool(report.assetsFileExists)}`, `Barrel re-exports \`./assets\`: ${formatBool(report.barrelReExportsAssets)}`, `\`provideDbxAssetLoader()\` called in \`src/root.app.config.ts\`: ${formatBool(report.providerWiredInApp)}`, "", `## Assets (${report.assets.length})`];
37655
+ const lines = [
37656
+ `# App assets \u2014 ${basename12}`,
37657
+ "",
37658
+ `Component: \`${report.componentDir}\``,
37659
+ `App: \`${report.apiDir}\``,
37660
+ "",
37661
+ `\`src/lib/assets.ts\`: ${formatBool(report.assetsFileExists)}`,
37662
+ `Barrel re-exports \`./assets\`: ${formatBool(report.barrelReExportsAssets)}`,
37663
+ `\`provideDbxAssetLoader()\` called in \`src/root.app.config.ts\`: ${formatBool(report.providerWiredInApp)}`,
37664
+ "",
37665
+ `## Assets (${report.assets.length})`
37666
+ ];
37409
37667
  if (report.assets.length === 0) {
37410
37668
  lines.push("", "_None found._");
37411
37669
  } else {
@@ -37796,7 +38054,18 @@ function formatReportAsJson6(report) {
37796
38054
  function formatReportAsMarkdown6(report) {
37797
38055
  const basename12 = report.componentDir.split("/").pop() ?? report.componentDir;
37798
38056
  const uploadServiceFactoryText = report.uploadServiceFactoryName ? code7(report.uploadServiceFactoryName) : "_Not defined._";
37799
- const lines = [`# App storagefiles \u2014 ${basename12}`, "", `Component: \`${report.componentDir}\``, `API: \`${report.apiDir}\``, "", `Upload service factory: ${uploadServiceFactoryText}`, `Wired via \`StorageFileInitializeFromUploadService\` provider: ${formatBool3(report.uploadServiceWiredInApi)}`, `Processing handler call present: ${formatBool3(report.processingHandlerWiredInApi)}`, "", `## Purposes (${report.purposes.length})`];
38057
+ const lines = [
38058
+ `# App storagefiles \u2014 ${basename12}`,
38059
+ "",
38060
+ `Component: \`${report.componentDir}\``,
38061
+ `API: \`${report.apiDir}\``,
38062
+ "",
38063
+ `Upload service factory: ${uploadServiceFactoryText}`,
38064
+ `Wired via \`StorageFileInitializeFromUploadService\` provider: ${formatBool3(report.uploadServiceWiredInApi)}`,
38065
+ `Processing handler call present: ${formatBool3(report.processingHandlerWiredInApi)}`,
38066
+ "",
38067
+ `## Purposes (${report.purposes.length})`
38068
+ ];
37800
38069
  if (report.purposes.length === 0) {
37801
38070
  lines.push("", "_None found._");
37802
38071
  } else {
@@ -38745,11 +39014,12 @@ function matchKindForRadius(exact, raw) {
38745
39014
  if (exact.defaults.light === raw || exact.defaults.dark === raw) return "(exact-match design-system corner token).";
38746
39015
  return "(idiomatic full / pill radius \u2014 design system token).";
38747
39016
  }
38748
- function findExactRoleToken(registry, raw, role) {
39017
+ function findExactRoleToken(params) {
39018
+ const { registry, raw, role, isAllowed } = params;
38749
39019
  const candidates = registry.byRole.get(role) ?? [];
38750
39020
  let result;
38751
39021
  for (const entry of candidates) {
38752
- if (entry.defaults.light === raw || entry.defaults.dark === raw) {
39022
+ if ((entry.defaults.light === raw || entry.defaults.dark === raw) && (isAllowed === void 0 || isAllowed(entry))) {
38753
39023
  result = entry;
38754
39024
  break;
38755
39025
  }
@@ -38894,7 +39164,7 @@ var hardcodedRadius = (input) => {
38894
39164
  const colonIndex = input.scss.indexOf(":", match.index);
38895
39165
  if (colonIndex !== -1 && isInsideScssVarDeclaration(input.scss, colonIndex)) continue;
38896
39166
  const raw = match[1];
38897
- let exact = findExactRoleToken(input.tokenRegistry, raw, "radius");
39167
+ let exact = findExactRoleToken({ registry: input.tokenRegistry, raw, role: "radius" });
38898
39168
  if (exact === void 0 && FULL_RADIUS_SYNONYMS.has(raw)) {
38899
39169
  exact = input.tokenRegistry.findByCssVariable("--mat-sys-corner-full");
38900
39170
  }
@@ -38925,7 +39195,7 @@ var hardcodedShadow = (input) => {
38925
39195
  if (match.index === void 0) continue;
38926
39196
  const raw = match[1].trim();
38927
39197
  if (raw.startsWith("var(") || raw === "none") continue;
38928
- const exact = findExactRoleToken(input.tokenRegistry, raw, "elevation");
39198
+ const exact = findExactRoleToken({ registry: input.tokenRegistry, raw, role: "elevation" });
38929
39199
  const fix = exact === void 0 ? 'Hardcoded `box-shadow:` declarations should map to one of the `--mat-sys-level0..5` elevation tokens, or wrap the surface in `<dbx-content-box [elevate]="true">` so the shadow comes from the design system.' : `Replace this hardcoded shadow with \`box-shadow: var(${exact.cssVariable})\` or use \`<dbx-content-box [elevate]="true">\` to apply the elevation through the wrapper component.`;
38930
39200
  matches.push({
38931
39201
  id: "hardcoded-shadow",
@@ -38970,12 +39240,13 @@ var hardcodedHintColor = (input) => {
38970
39240
  return matches;
38971
39241
  };
38972
39242
  var HARDCODED_PADDING_RE = /(?:padding|margin)\s*(?:-(?:top|right|bottom|left))?\s*:\s*(\d+(?:\.\d+)?px)(?!\s*\))/gi;
39243
+ var DBX_PADDING_SCALE_RE = /^--dbx-padding-\d+$/;
38973
39244
  var hardcodedPadding = (input) => {
38974
39245
  const matches = [];
38975
39246
  for (const match of input.scss.matchAll(HARDCODED_PADDING_RE)) {
38976
39247
  if (match.index === void 0) continue;
38977
39248
  const raw = match[1];
38978
- const exact = findExactRoleToken(input.tokenRegistry, raw, "spacing");
39249
+ const exact = findExactRoleToken({ registry: input.tokenRegistry, raw, role: "spacing", isAllowed: (entry) => DBX_PADDING_SCALE_RE.test(entry.cssVariable) });
38979
39250
  if (exact !== void 0) {
38980
39251
  const utility = exact.utilityClasses?.[0];
38981
39252
  const fix = ["Replace `" + match[0].trim() + "` with `var(" + exact.cssVariable + ")`" + (utility === void 0 ? "" : ` (or use the \`${utility}\` utility class on the host element)`) + "."].join("\n");
@@ -41473,7 +41744,14 @@ function rootDocumentStoreBody(args) {
41473
41744
  if (args.functionsClass !== void 0) {
41474
41745
  packageImports.push(args.functionsClass);
41475
41746
  }
41476
- const lines = [`import { Injectable, inject } from '@angular/core';`, `import { ${joinUnique(dbxFirebaseImports)} } from '@dereekb/dbx-firebase';`, `import { ${joinUnique(packageImports)} } from '${args.firebasePackage}';`, "", `@Injectable()`, `export class ${storeClass} extends ${baseClass}<${args.modelName}, ${docType}> {`];
41747
+ const lines = [
41748
+ `import { Injectable, inject } from '@angular/core';`,
41749
+ `import { ${joinUnique(dbxFirebaseImports)} } from '@dereekb/dbx-firebase';`,
41750
+ `import { ${joinUnique(packageImports)} } from '${args.firebasePackage}';`,
41751
+ "",
41752
+ `@Injectable()`,
41753
+ `export class ${storeClass} extends ${baseClass}<${args.modelName}, ${docType}> {`
41754
+ ];
41477
41755
  if (args.functionsClass !== void 0) {
41478
41756
  lines.push(` readonly ${functionsFieldName(args.functionsClass)} = inject(${args.functionsClass});`, "");
41479
41757
  }
@@ -41516,11 +41794,29 @@ function subDocumentStoreBody(args) {
41516
41794
  if (args.functionsClass !== void 0) {
41517
41795
  packageImports.push(args.functionsClass);
41518
41796
  }
41519
- const lines = [`import { Injectable, inject } from '@angular/core';`, `import { ${joinUnique(dbxFirebaseImports)} } from '@dereekb/dbx-firebase';`, `import { ${joinUnique(packageImports)} } from '${args.firebasePackage}';`, `import { ${parentStore} } from './${parentBaseFile}.document.store';`, "", `@Injectable()`, `export class ${storeClass} extends ${baseClass}<${args.modelName}, ${parent.name}, ${docType}, ${parent.documentType}> {`];
41797
+ const lines = [
41798
+ `import { Injectable, inject } from '@angular/core';`,
41799
+ `import { ${joinUnique(dbxFirebaseImports)} } from '@dereekb/dbx-firebase';`,
41800
+ `import { ${joinUnique(packageImports)} } from '${args.firebasePackage}';`,
41801
+ `import { ${parentStore} } from './${parentBaseFile}.document.store';`,
41802
+ "",
41803
+ `@Injectable()`,
41804
+ `export class ${storeClass} extends ${baseClass}<${args.modelName}, ${parent.name}, ${docType}, ${parent.documentType}> {`
41805
+ ];
41520
41806
  if (args.functionsClass !== void 0) {
41521
41807
  lines.push(` readonly ${functionsFieldName(args.functionsClass)} = inject(${args.functionsClass});`, "");
41522
41808
  }
41523
- lines.push(` constructor() {`, ` const collections = inject(${collections});`, ` super({ collectionFactory: collections.${args.factoryAccessor}, firestoreCollectionLike: collections.${args.groupAccessor} });`, ` const parent = inject(${parentStore}, { optional: true });`, "", ` if (parent) {`, ` this.setParentStore(parent);`, ` }`, ` }`);
41809
+ lines.push(
41810
+ ` constructor() {`,
41811
+ ` const collections = inject(${collections});`,
41812
+ ` super({ collectionFactory: collections.${args.factoryAccessor}, firestoreCollectionLike: collections.${args.groupAccessor} });`,
41813
+ ` const parent = inject(${parentStore}, { optional: true });`,
41814
+ "",
41815
+ ` if (parent) {`,
41816
+ ` this.setParentStore(parent);`,
41817
+ ` }`,
41818
+ ` }`
41819
+ );
41524
41820
  const crudLines = renderCrudLines(args, "this");
41525
41821
  if (crudLines.length > 0) {
41526
41822
  lines.push("", ...crudLines);
@@ -41565,7 +41861,14 @@ function systemStateAccessorBody(args) {
41565
41861
  if (args.functionsClass !== void 0) {
41566
41862
  packageImports.push(args.functionsClass);
41567
41863
  }
41568
- const lines = [`import { Injectable, inject } from '@angular/core';`, `import { ${joinUnique(dbxFirebaseImports)} } from '@dereekb/dbx-firebase';`, `import { ${joinUnique(packageImports)} } from '${args.firebasePackage}';`, "", `@Injectable()`, `export class ${accessorClass} extends AbstractSystemStateDocumentStoreAccessor<${dataType}> {`];
41864
+ const lines = [
41865
+ `import { Injectable, inject } from '@angular/core';`,
41866
+ `import { ${joinUnique(dbxFirebaseImports)} } from '@dereekb/dbx-firebase';`,
41867
+ `import { ${joinUnique(packageImports)} } from '${args.firebasePackage}';`,
41868
+ "",
41869
+ `@Injectable()`,
41870
+ `export class ${accessorClass} extends AbstractSystemStateDocumentStoreAccessor<${dataType}> {`
41871
+ ];
41569
41872
  if (args.functionsClass !== void 0) {
41570
41873
  lines.push(` readonly ${functionsFieldName(args.functionsClass)} = inject(${args.functionsClass});`, "");
41571
41874
  }
@@ -42148,7 +42451,17 @@ var ForwardArgsType = type62({
42148
42451
  });
42149
42452
  var TOOL8 = {
42150
42453
  name: "dbx_model_fixture_forward",
42151
- description: ["Insert the missing Fixture forwarder methods for one model in `<apiDir>/src/test/fixture.ts`. Every public Instance method that lacks a Fixture method gains a thin wrapper:", "", "```ts", "async <name>(<paramText>): <returnType> {", " return this.instance.<name>(<args>);", "}", "```", "", "Existing forwarders are left untouched (idempotent). Pass `methods` to limit the run to specific method names; omit to forward everything that's missing."].join("\n"),
42454
+ description: [
42455
+ "Insert the missing Fixture forwarder methods for one model in `<apiDir>/src/test/fixture.ts`. Every public Instance method that lacks a Fixture method gains a thin wrapper:",
42456
+ "",
42457
+ "```ts",
42458
+ "async <name>(<paramText>): <returnType> {",
42459
+ " return this.instance.<name>(<args>);",
42460
+ "}",
42461
+ "```",
42462
+ "",
42463
+ "Existing forwarders are left untouched (idempotent). Pass `methods` to limit the run to specific method names; omit to forward everything that's missing."
42464
+ ].join("\n"),
42152
42465
  inputSchema: {
42153
42466
  type: "object",
42154
42467
  properties: {
@@ -42627,7 +42940,11 @@ function bucketSummary(group, bucket) {
42627
42940
  }
42628
42941
  return "Multi-step scenarios using fixture chains (mirrors real workflows). Add focused buckets as `" + group + ".scenario.<sub>.spec.ts` (chain subgroups freely, e.g. `.scenario.requirement.worker.spec.ts`).";
42629
42942
  }
42630
- var DRIFT_RULES = ["`<group>.<sub>.crud.spec.ts` \u2192 rename to `<group>.crud.<sub>.spec.ts` (`crud` belongs directly after the group name).", "`<group>.<sub>.scenario.spec.ts` \u2192 rename to `<group>.scenario.<sub>.spec.ts` (`scenario` belongs directly after the group name).", "`<group>.<rest>.spec.ts` with no `crud` / `scenario` segment \u2192 add one (default to `scenario` when in doubt)."];
42943
+ var DRIFT_RULES = [
42944
+ "`<group>.<sub>.crud.spec.ts` \u2192 rename to `<group>.crud.<sub>.spec.ts` (`crud` belongs directly after the group name).",
42945
+ "`<group>.<sub>.scenario.spec.ts` \u2192 rename to `<group>.scenario.<sub>.spec.ts` (`scenario` belongs directly after the group name).",
42946
+ "`<group>.<rest>.spec.ts` with no `crud` / `scenario` segment \u2192 add one (default to `scenario` when in doubt)."
42947
+ ];
42631
42948
  function formatMarkdown(report) {
42632
42949
  const lines = [];
42633
42950
  lines.push(`# Spec convention for \`${report.group}\``, "", `Tests live under \`${report.apiDir}/src/app/function/${report.group}/\`. Canonical name segments:`, "");
@@ -43407,7 +43724,14 @@ var ARCHETYPE_RECOMMEND_TOOL = {
43407
43724
  import { type as type70 } from "arktype";
43408
43725
  var DBX_MODEL_ARCHETYPE_LOOKUP_TOOL = {
43409
43726
  name: "dbx_model_archetype_lookup",
43410
- description: ["Look up an archetype in the model-archetype catalog. Accepts:", ' \u2022 a slug (`"root-entity"`, `"denormalised-aggregate"`, \u2026);', ' \u2022 the literal `"list"` for the full catalog grouped by family.', "", "Optional inputs:", ' \u2022 `axes`: optional axis filter (`{ "subPurpose": "private" }`) \u2014 when set, the description shifts to focus on the matched axis.'].join("\n"),
43727
+ description: [
43728
+ "Look up an archetype in the model-archetype catalog. Accepts:",
43729
+ ' \u2022 a slug (`"root-entity"`, `"denormalised-aggregate"`, \u2026);',
43730
+ ' \u2022 the literal `"list"` for the full catalog grouped by family.',
43731
+ "",
43732
+ "Optional inputs:",
43733
+ ' \u2022 `axes`: optional axis filter (`{ "subPurpose": "private" }`) \u2014 when set, the description shifts to focus on the matched axis.'
43734
+ ].join("\n"),
43411
43735
  inputSchema: {
43412
43736
  type: "object",
43413
43737
  properties: {
@@ -44800,7 +45124,18 @@ function formatStateInteraction(entry) {
44800
45124
  return result;
44801
45125
  }
44802
45126
  function formatDirectiveEntry(entry, depth) {
44803
- const lines = [`# ${entry.className}`, "", entry.description, "", bullet("selector", `\`${entry.selector}\``), bullet("module", `\`${entry.module}\``), bullet("slug", `\`${entry.slug}\``), bullet("produces context", entry.producesContext ? "yes (provides `ActionContextStore` via DI)" : "no"), bullet("consumes context", entry.consumesContext ? "yes (injects `DbxActionContextStoreSourceInstance`)" : "no"), ""];
45127
+ const lines = [
45128
+ `# ${entry.className}`,
45129
+ "",
45130
+ entry.description,
45131
+ "",
45132
+ bullet("selector", `\`${entry.selector}\``),
45133
+ bullet("module", `\`${entry.module}\``),
45134
+ bullet("slug", `\`${entry.slug}\``),
45135
+ bullet("produces context", entry.producesContext ? "yes (provides `ActionContextStore` via DI)" : "no"),
45136
+ bullet("consumes context", entry.consumesContext ? "yes (injects `DbxActionContextStoreSourceInstance`)" : "no"),
45137
+ ""
45138
+ ];
44804
45139
  if (depth === "full") {
44805
45140
  lines.push("## Inputs", "", entry.inputs.length > 0 ? formatInputsTable2(entry.inputs) : "_No inputs._", "");
44806
45141
  if (entry.outputs.length > 0) {
@@ -45205,8 +45540,7 @@ import { type Work } from '@dereekb/rxjs';
45205
45540
  dbxActionForm
45206
45541
  [dbxFormSource]="settings$"></my-settings-form>
45207
45542
  </div>
45208
- \`,
45209
- standalone: true
45543
+ \`
45210
45544
  })
45211
45545
  export class SettingsAutosaveComponent {
45212
45546
  private readonly store = inject(SettingsDocumentStore);
@@ -45250,8 +45584,7 @@ import { type Work } from '@dereekb/rxjs';
45250
45584
  icon="delete"
45251
45585
  dbxActionButton></dbx-button>
45252
45586
  </ng-container>
45253
- \`,
45254
- standalone: true
45587
+ \`
45255
45588
  })
45256
45589
  export class DeleteAccountButtonComponent {
45257
45590
  private readonly accountStore = inject(AccountDocumentStore);
@@ -45315,8 +45648,7 @@ import { type Work } from '@dereekb/rxjs';
45315
45648
  <dbx-button text="Archive" dbxActionButton></dbx-button>
45316
45649
  </ng-container>
45317
45650
  </div>
45318
- \`,
45319
- standalone: true
45651
+ \`
45320
45652
  })
45321
45653
  export class ContentToolbarComponent {
45322
45654
  private readonly store = inject(ArticleDocumentStore);
@@ -45370,8 +45702,7 @@ import { type WorkUsingContext } from '@dereekb/rxjs';
45370
45702
  <dbx-button [raised]="true" text="Save" dbxActionButton></dbx-button>
45371
45703
  <dbx-error dbxActionError></dbx-error>
45372
45704
  </form>
45373
- \`,
45374
- standalone: true
45705
+ \`
45375
45706
  })
45376
45707
  export class EditProfilePageComponent {
45377
45708
  private readonly profileStore = inject(ProfileDocumentStore);
@@ -45441,8 +45772,7 @@ type ConsentDecision = { readonly outcome: 'approved'; readonly value: ConsentFo
45441
45772
  }
45442
45773
  </div>
45443
45774
  }
45444
- \`,
45445
- standalone: true
45775
+ \`
45446
45776
  })
45447
45777
  export class ConsentPageComponent {
45448
45778
  readonly defaults$ = this.store.defaults$;
@@ -45489,8 +45819,7 @@ import { DbxActionContextMachine } from '@dereekb/dbx-core';
45489
45819
  <div [dbxActionSource]="actionSource()">
45490
45820
  <child-component></child-component>
45491
45821
  </div>
45492
- \`,
45493
- standalone: true
45822
+ \`
45494
45823
  })
45495
45824
  export class ParentComponent {
45496
45825
  readonly machine = new DbxActionContextMachine<MyValue, MyResult>();
@@ -45510,8 +45839,7 @@ export class ParentComponent {
45510
45839
  <ng-container dbxAction dbxActionValue="hello">
45511
45840
  <dbx-button [raised]="true" text="Submit" dbxActionButton></dbx-button>
45512
45841
  </ng-container>
45513
- \`,
45514
- standalone: true
45842
+ \`
45515
45843
  })
45516
45844
  export class ChildComponent {}`
45517
45845
  },
@@ -45554,8 +45882,7 @@ import { type IsEqualFunction } from '@dereekb/rxjs';
45554
45882
  <my-tag-form #form></my-tag-form>
45555
45883
  <dbx-button text="Save" dbxActionButton></dbx-button>
45556
45884
  </div>
45557
- \`,
45558
- standalone: true
45885
+ \`
45559
45886
  })
45560
45887
  export class TagEditorComponent {
45561
45888
  private readonly store = inject(TagCollectionStore);
@@ -45571,7 +45898,15 @@ export class TagEditorComponent {
45571
45898
  };
45572
45899
 
45573
45900
  // packages/dbx-components-mcp/src/tools/data/patterns/action-patterns.ts
45574
- var ACTION_EXAMPLE_PATTERNS = [ACTION_PATTERN_BUTTON_CONFIRM_DELETE, ACTION_PATTERN_FORM_SUBMIT, ACTION_PATTERN_FORM_WITH_SECONDARY, ACTION_PATTERN_AUTO_TRIGGER_ON_MODIFY, ACTION_PATTERN_DISABLED_BY_KEY, ACTION_PATTERN_VALUE_GETTER_ON_TRIGGER, ACTION_PATTERN_PROVIDE_CONTEXT_UP];
45901
+ var ACTION_EXAMPLE_PATTERNS = [
45902
+ ACTION_PATTERN_BUTTON_CONFIRM_DELETE,
45903
+ ACTION_PATTERN_FORM_SUBMIT,
45904
+ ACTION_PATTERN_FORM_WITH_SECONDARY,
45905
+ ACTION_PATTERN_AUTO_TRIGGER_ON_MODIFY,
45906
+ ACTION_PATTERN_DISABLED_BY_KEY,
45907
+ ACTION_PATTERN_VALUE_GETTER_ON_TRIGGER,
45908
+ ACTION_PATTERN_PROVIDE_CONTEXT_UP
45909
+ ];
45575
45910
  function getActionExamplePattern(slug) {
45576
45911
  const lowered = slug.trim().toLowerCase();
45577
45912
  return ACTION_EXAMPLE_PATTERNS.find((p) => p.slug === lowered);
@@ -45895,7 +46230,27 @@ function renderScaffold3(args) {
45895
46230
  const directivesUsed = buildDirectivesUsed(args);
45896
46231
  const examplePattern = relatedExamplePattern(args);
45897
46232
  const lines = [];
45898
- lines.push(`# Action scaffold \u2014 ${args.useCase}`, "", `Trigger: \`${args.trigger}\` \xB7 Confirm: \`${args.confirm}\` \xB7 Success feedback: \`${args.successFeedback}\` \xB7 Context: \`${args.contextProvider}\``, "", "## Template", "", "```html", template, "```", "", "## Component class", "", "```ts", handler, "```", "", "## Imports", "", "```ts");
46233
+ lines.push(
46234
+ `# Action scaffold \u2014 ${args.useCase}`,
46235
+ "",
46236
+ `Trigger: \`${args.trigger}\` \xB7 Confirm: \`${args.confirm}\` \xB7 Success feedback: \`${args.successFeedback}\` \xB7 Context: \`${args.contextProvider}\``,
46237
+ "",
46238
+ "## Template",
46239
+ "",
46240
+ "```html",
46241
+ template,
46242
+ "```",
46243
+ "",
46244
+ "## Component class",
46245
+ "",
46246
+ "```ts",
46247
+ handler,
46248
+ "```",
46249
+ "",
46250
+ "## Imports",
46251
+ "",
46252
+ "```ts"
46253
+ );
45899
46254
  for (const line of imports) {
45900
46255
  lines.push(line);
45901
46256
  }
@@ -46339,7 +46694,13 @@ function formatMissingRouteModelLine(param) {
46339
46694
  // packages/dbx-components-mcp/src/tools/route-lookup.tool.ts
46340
46695
  var DBX_ROUTE_LOOKUP_TOOL = {
46341
46696
  name: "dbx_route_lookup",
46342
- description: ["Look up a single UIRouter state by name, full URL, or component class name. Returns the state's URL, component, parent chain, params, resolves, siblings, and children.", "", "Provide at least one of `sources` / `paths` / `glob` to point the tool at the source set, plus a `topic`.", "", "Resolution order: exact state name \u2192 URL path \u2192 component class name \u2192 fuzzy substring (returns up to 5 candidates)."].join("\n"),
46697
+ description: [
46698
+ "Look up a single UIRouter state by name, full URL, or component class name. Returns the state's URL, component, parent chain, params, resolves, siblings, and children.",
46699
+ "",
46700
+ "Provide at least one of `sources` / `paths` / `glob` to point the tool at the source set, plus a `topic`.",
46701
+ "",
46702
+ "Resolution order: exact state name \u2192 URL path \u2192 component class name \u2192 fuzzy substring (returns up to 5 candidates)."
46703
+ ].join("\n"),
46343
46704
  inputSchema: {
46344
46705
  type: "object",
46345
46706
  properties: {
@@ -46599,7 +46960,11 @@ var ROUTE_LOOKUP_TOOL = {
46599
46960
  import { type as type82 } from "arktype";
46600
46961
  var DBX_ROUTE_SEARCH_TOOL = {
46601
46962
  name: "dbx_route_search",
46602
- description: ["Search the UIRouter state tree of a dbx-components app. Matches against state names, full URLs, component class names, resolve keys, and source-file paths. Returns the top 10 results with state name, URL, component, and source location.", "", "Provide at least one of `sources` / `paths` / `glob` plus a `query`. `scope` narrows to a single axis (`name`, `url`, `component`, `resolve`)."].join("\n"),
46963
+ description: [
46964
+ "Search the UIRouter state tree of a dbx-components app. Matches against state names, full URLs, component class names, resolve keys, and source-file paths. Returns the top 10 results with state name, URL, component, and source location.",
46965
+ "",
46966
+ "Provide at least one of `sources` / `paths` / `glob` plus a `query`. `scope` narrows to a single axis (`name`, `url`, `component`, `resolve`)."
46967
+ ].join("\n"),
46603
46968
  inputSchema: {
46604
46969
  type: "object",
46605
46970
  properties: {
@@ -47691,7 +48056,25 @@ function formatScaffold2(args) {
47691
48056
  if (args.presetKeys.length > 0) {
47692
48057
  lines.push("## Presets", "", "```ts", renderPresetConstants(args), "```", "");
47693
48058
  }
47694
- lines.push("## Template", "", "```html", renderTemplate(args), "```", "", "## Component class", "", "```ts", ...renderImports(args), "", renderClass(args), "```", "", "## Notes", "", "- The outer `[dbxFilterSourceConnector]` provides both `FilterSource` and `FilterSourceConnector`. Children that own the form use `[dbxFilterSource]` + `[dbxFilterConnectSource]` to wire up.");
48059
+ lines.push(
48060
+ "## Template",
48061
+ "",
48062
+ "```html",
48063
+ renderTemplate(args),
48064
+ "```",
48065
+ "",
48066
+ "## Component class",
48067
+ "",
48068
+ "```ts",
48069
+ ...renderImports(args),
48070
+ "",
48071
+ renderClass(args),
48072
+ "```",
48073
+ "",
48074
+ "## Notes",
48075
+ "",
48076
+ "- The outer `[dbxFilterSourceConnector]` provides both `FilterSource` and `FilterSourceConnector`. Children that own the form use `[dbxFilterSource]` + `[dbxFilterConnectSource]` to wire up."
48077
+ );
47695
48078
  if (args.presetKeys.length > 0) {
47696
48079
  lines.push("- Each `ClickableFilterPreset` listed in the preset array becomes a chip; clicking sets `presetValue` on the filter source.");
47697
48080
  }
@@ -47758,7 +48141,20 @@ function formatArgsTable(args) {
47758
48141
  return lines.join("\n");
47759
48142
  }
47760
48143
  function formatEntry4(entry, depth) {
47761
- const lines = [`# ${entry.className}`, "", entry.description, "", bullet3("slug", `\`${entry.slug}\``), bullet3("pipe", `\`${entry.pipeName}\``), bullet3("category", `\`${entry.category}\``), bullet3("purity", `\`${entry.purity}\``), bullet3("input", `\`${entry.inputType}\``), bullet3("output", `\`${entry.outputType}\``), bullet3("module", `\`${entry.module}\``), ""];
48144
+ const lines = [
48145
+ `# ${entry.className}`,
48146
+ "",
48147
+ entry.description,
48148
+ "",
48149
+ bullet3("slug", `\`${entry.slug}\``),
48150
+ bullet3("pipe", `\`${entry.pipeName}\``),
48151
+ bullet3("category", `\`${entry.category}\``),
48152
+ bullet3("purity", `\`${entry.purity}\``),
48153
+ bullet3("input", `\`${entry.inputType}\``),
48154
+ bullet3("output", `\`${entry.outputType}\``),
48155
+ bullet3("module", `\`${entry.module}\``),
48156
+ ""
48157
+ ];
47762
48158
  if (depth === "full") {
47763
48159
  if (entry.args.length > 0) {
47764
48160
  lines.push("## Args", "", formatArgsTable(entry.args), "");
@@ -47920,7 +48316,20 @@ function formatSearchResults5(input) {
47920
48316
  const lines = [`# Search: \`${query}\``, "", `Tokens: \`${tokenDisplay}\` \xB7 ${hits.length} result${hits.length === 1 ? "" : "s"}`, ""];
47921
48317
  for (const hit of hits) {
47922
48318
  const entry = hit.entry;
47923
- lines.push(`## \`${entry.slug}\` \xB7 ${entry.category} \xB7 score ${hit.score}`, "", `- **pipe:** \`${entry.pipeName}\``, `- **class:** \`${entry.className}\``, `- **input:** \`${entry.inputType}\``, `- **output:** \`${entry.outputType}\``, `- **matched:** \`${hit.matchedTokens.join(", ")}\``, "", entry.description, "", `\u2192 \`dbx_pipe_lookup topic="${entry.slug}"\` for full docs.`, "");
48319
+ lines.push(
48320
+ `## \`${entry.slug}\` \xB7 ${entry.category} \xB7 score ${hit.score}`,
48321
+ "",
48322
+ `- **pipe:** \`${entry.pipeName}\``,
48323
+ `- **class:** \`${entry.className}\``,
48324
+ `- **input:** \`${entry.inputType}\``,
48325
+ `- **output:** \`${entry.outputType}\``,
48326
+ `- **matched:** \`${hit.matchedTokens.join(", ")}\``,
48327
+ "",
48328
+ entry.description,
48329
+ "",
48330
+ `\u2192 \`dbx_pipe_lookup topic="${entry.slug}"\` for full docs.`,
48331
+ ""
48332
+ );
47924
48333
  }
47925
48334
  return lines.join("\n").trimEnd();
47926
48335
  }
@@ -48196,7 +48605,20 @@ function formatCatalogHit(input) {
48196
48605
  const tagDisplay = entry.tags.length > 0 ? `
48197
48606
  - **tags:** ${tagBadges}` : "";
48198
48607
  const badge = options.headerBadge ?? "";
48199
- return [`## \`${entry.slug}\` \xB7 \`${entry.name}\` \xB7 ${entry.kind}${badge} \xB7 score ${hit.score}`, "", `- **module:** \`${entry.module}\``, `- **category:** \`${entry.category}\``, `- **subpath:** \`${entry.subpath}\``, `- **signature:** \`${entry.signature}\``, `- **matched:** \`${hit.matchedTokens.join(", ")}\`${tagDisplay}`, "", entry.description.split("\n")[0], "", `\u2192 \`${options.lookupTool} topic="${entry.slug}"\` for full docs.`, ""];
48608
+ return [
48609
+ `## \`${entry.slug}\` \xB7 \`${entry.name}\` \xB7 ${entry.kind}${badge} \xB7 score ${hit.score}`,
48610
+ "",
48611
+ `- **module:** \`${entry.module}\``,
48612
+ `- **category:** \`${entry.category}\``,
48613
+ `- **subpath:** \`${entry.subpath}\``,
48614
+ `- **signature:** \`${entry.signature}\``,
48615
+ `- **matched:** \`${hit.matchedTokens.join(", ")}\`${tagDisplay}`,
48616
+ "",
48617
+ entry.description.split("\n")[0],
48618
+ "",
48619
+ `\u2192 \`${options.lookupTool} topic="${entry.slug}"\` for full docs.`,
48620
+ ""
48621
+ ];
48200
48622
  }
48201
48623
  function formatCatalogSearchResults(input) {
48202
48624
  const { query, tokens, hits, filters, entityLabel, emptyHint, hitOptions } = input;
@@ -48372,7 +48794,20 @@ function appendFullEntrySections2(lines, entry) {
48372
48794
  }
48373
48795
  function formatEntry6(entry, depth) {
48374
48796
  const heading = `# ${entry.name}`;
48375
- const lines = [heading, "", entry.description, "", bullet5("slug", `\`${entry.slug}\``), bullet5("kind", `\`${entry.kind}\``), bullet5("category", `\`${entry.category}\``), bullet5("module", `\`${entry.module}\``), bullet5("subpath", `\`${entry.subpath}\``), bullet5("optional", entry.optional ? "yes (use this for `Maybe<T>` fields)" : "no (required)"), bullet5("signature", `\`${entry.signature}\``), ""];
48797
+ const lines = [
48798
+ heading,
48799
+ "",
48800
+ entry.description,
48801
+ "",
48802
+ bullet5("slug", `\`${entry.slug}\``),
48803
+ bullet5("kind", `\`${entry.kind}\``),
48804
+ bullet5("category", `\`${entry.category}\``),
48805
+ bullet5("module", `\`${entry.module}\``),
48806
+ bullet5("subpath", `\`${entry.subpath}\``),
48807
+ bullet5("optional", entry.optional ? "yes (use this for `Maybe<T>` fields)" : "no (required)"),
48808
+ bullet5("signature", `\`${entry.signature}\``),
48809
+ ""
48810
+ ];
48376
48811
  appendDeprecationNotice2(lines, entry);
48377
48812
  if (depth === "full") {
48378
48813
  appendFullEntrySections2(lines, entry);
@@ -49702,7 +50137,14 @@ function appendTaggedSections(lines, tagged) {
49702
50137
  }
49703
50138
  function appendUntaggedSection(lines, untagged) {
49704
50139
  if (untagged.length === 0) return;
49705
- lines.push("## Untagged candidates", "", "Exported functions returning `FirestoreQueryConstraint[]` without an `@dbxModelFirebaseIndex` block. Tag them so the validator can verify their indexes \u2014 or annotate with `@dbxModelFirebaseIndexSkip` to record that they were considered.", "", "| Function | File |", "| --- | --- |");
50140
+ lines.push(
50141
+ "## Untagged candidates",
50142
+ "",
50143
+ "Exported functions returning `FirestoreQueryConstraint[]` without an `@dbxModelFirebaseIndex` block. Tag them so the validator can verify their indexes \u2014 or annotate with `@dbxModelFirebaseIndexSkip` to record that they were considered.",
50144
+ "",
50145
+ "| Function | File |",
50146
+ "| --- | --- |"
50147
+ );
49706
50148
  for (const candidate of untagged) {
49707
50149
  lines.push(`| \`${candidate.name}\` | \`${candidate.subpath}:${candidate.line}\` |`);
49708
50150
  }
@@ -50402,7 +50844,14 @@ function formatReportAsMarkdown17(report) {
50402
50844
  } else {
50403
50845
  lines.push("## \u2705 In sync", "");
50404
50846
  }
50405
- lines.push(`- generated composites: ${report.generatedComposites}`, `- existing composites: ${report.existingComposites}`, `- generated fieldOverrides: ${report.generatedFieldOverrides}`, `- existing fieldOverrides: ${report.existingFieldOverrides}`, `- ${report.errorCount} error(s), ${report.warningCount} warning(s)`, "");
50847
+ lines.push(
50848
+ `- generated composites: ${report.generatedComposites}`,
50849
+ `- existing composites: ${report.existingComposites}`,
50850
+ `- generated fieldOverrides: ${report.generatedFieldOverrides}`,
50851
+ `- existing fieldOverrides: ${report.existingFieldOverrides}`,
50852
+ `- ${report.errorCount} error(s), ${report.warningCount} warning(s)`,
50853
+ ""
50854
+ );
50406
50855
  if (report.violations.length > 0) {
50407
50856
  appendViolationSection(
50408
50857
  lines,
@@ -50820,7 +51269,10 @@ function renderStorageFilePurpose(input, ctx) {
50820
51269
  tokens: ctx.tokens,
50821
51270
  files: [componentFile, handlerFile],
50822
51271
  wiring,
50823
- summary: applyTokens(`Scaffolds a new \`<<SCREAMING>>_PURPOSE\` storage-file purpose end-to-end: component-side constants + path helpers (append to \`storagefile.ts\`) and an API-side upload-initializer factory in \`handlers/\`. After applying, run \`dbx_validate_app_storagefiles\` and \`dbx_validate_storagefile_folder\` against your project \u2014 both should PASS.`, ctx)
51272
+ summary: applyTokens(
51273
+ `Scaffolds a new \`<<SCREAMING>>_PURPOSE\` storage-file purpose end-to-end: component-side constants + path helpers (append to \`storagefile.ts\`) and an API-side upload-initializer factory in \`handlers/\`. After applying, run \`dbx_validate_app_storagefiles\` and \`dbx_validate_storagefile_folder\` against your project \u2014 both should PASS.`,
51274
+ ctx
51275
+ )
50824
51276
  };
50825
51277
  return result;
50826
51278
  }
@@ -50828,19 +51280,28 @@ function renderNotificationTemplate(input, ctx) {
50828
51280
  const componentFile = {
50829
51281
  status: "append",
50830
51282
  path: applyTokens("<<componentDir>>/src/lib/model/notification/notification.ts", ctx),
50831
- description: applyTokens(`Append the \`<<Pascal>>\` notification block to \`notification.ts\`: type constant + info + data interface + template factory. Reuses the file's existing imports \u2014 no new top-level imports required as long as \`NotificationTemplateType\`, \`NotificationTemplateTypeInfo\`, \`CreateNotificationTemplate\`, \`createNotificationTemplate\`, \`FirebaseAuthUserId\`, \`profileIdentity\`, and \`ProfileDocument\` are already in scope.`, ctx),
51283
+ description: applyTokens(
51284
+ `Append the \`<<Pascal>>\` notification block to \`notification.ts\`: type constant + info + data interface + template factory. Reuses the file's existing imports \u2014 no new top-level imports required as long as \`NotificationTemplateType\`, \`NotificationTemplateTypeInfo\`, \`CreateNotificationTemplate\`, \`createNotificationTemplate\`, \`FirebaseAuthUserId\`, \`profileIdentity\`, and \`ProfileDocument\` are already in scope.`,
51285
+ ctx
51286
+ ),
50832
51287
  content: applyTokens(NOTIFICATION_TEMPLATE_COMPONENT_TEMPLATE, ctx)
50833
51288
  };
50834
51289
  const factoryFile = {
50835
51290
  status: "append",
50836
51291
  path: applyTokens("<<apiDir>>/src/app/common/model/notification/notification.factory.ts", ctx),
50837
- description: applyTokens(`Append the \`<<appCamel>><<Pascal>>NotificationFactory\` to \`notification.factory.ts\`. Reuses the file's existing imports for \`NotificationMessageFunctionFactoryConfig\`, \`NotificationMessageInputContext\`, \`NotificationMessageContent\`, \`NotificationMessage\`, \`notificationMessageFunction\`, and \`NotificationTemplateServiceTypeConfig\`.`, ctx),
51292
+ description: applyTokens(
51293
+ `Append the \`<<appCamel>><<Pascal>>NotificationFactory\` to \`notification.factory.ts\`. Reuses the file's existing imports for \`NotificationMessageFunctionFactoryConfig\`, \`NotificationMessageInputContext\`, \`NotificationMessageContent\`, \`NotificationMessage\`, \`notificationMessageFunction\`, and \`NotificationTemplateServiceTypeConfig\`.`,
51294
+ ctx
51295
+ ),
50838
51296
  content: applyTokens(NOTIFICATION_TEMPLATE_FACTORY_TEMPLATE, ctx)
50839
51297
  };
50840
51298
  const wiring = [
50841
51299
  {
50842
51300
  file: applyTokens("<<apiDir>>/src/app/common/model/notification/notification.factory.ts", ctx),
50843
- description: applyTokens(`Two registrations are required: (1) add a call to \`<<appCamel>><<Pascal>>NotificationFactory(context)\` in the existing configs-array factory return list (so the handler is reachable through the template service); (2) add \`<<SCREAMING>>_NOTIFICATION_TEMPLATE_TYPE_INFO\` to the \`*_NOTIFICATION_TEMPLATE_TYPE_INFO_RECORD\` aggregator in \`notification.ts\` so the metadata path is wired.`, ctx),
51301
+ description: applyTokens(
51302
+ `Two registrations are required: (1) add a call to \`<<appCamel>><<Pascal>>NotificationFactory(context)\` in the existing configs-array factory return list (so the handler is reachable through the template service); (2) add \`<<SCREAMING>>_NOTIFICATION_TEMPLATE_TYPE_INFO\` to the \`*_NOTIFICATION_TEMPLATE_TYPE_INFO_RECORD\` aggregator in \`notification.ts\` so the metadata path is wired.`,
51303
+ ctx
51304
+ ),
50844
51305
  snippet: applyTokens(NOTIFICATION_TEMPLATE_WIRING_SNIPPET, ctx)
50845
51306
  }
50846
51307
  ];
@@ -50849,7 +51310,10 @@ function renderNotificationTemplate(input, ctx) {
50849
51310
  tokens: ctx.tokens,
50850
51311
  files: [componentFile, factoryFile],
50851
51312
  wiring,
50852
- summary: applyTokens(`Scaffolds a new \`<<SCREAMING>>_NOTIFICATION_TEMPLATE_TYPE\` end-to-end: component-side type + info + data + template factory (append to \`notification.ts\`) and an API-side template-service factory (append to \`notification.factory.ts\`). After applying the wiring, run \`dbx_notification_m_validate_app\` and \`dbx_notification_m_validate_folder\` against your project \u2014 both should PASS.`, ctx)
51313
+ summary: applyTokens(
51314
+ `Scaffolds a new \`<<SCREAMING>>_NOTIFICATION_TEMPLATE_TYPE\` end-to-end: component-side type + info + data + template factory (append to \`notification.ts\`) and an API-side template-service factory (append to \`notification.factory.ts\`). After applying the wiring, run \`dbx_notification_m_validate_app\` and \`dbx_notification_m_validate_folder\` against your project \u2014 both should PASS.`,
51315
+ ctx
51316
+ )
50853
51317
  };
50854
51318
  return result;
50855
51319
  }
@@ -50874,7 +51338,10 @@ function renderNotificationTask(input, ctx) {
50874
51338
  const wiring = [
50875
51339
  {
50876
51340
  file: applyTokens("<<apiDir>>/src/app/common/model/notification/notification.task.service.ts", ctx),
50877
- description: applyTokens(`Import the new factory, instantiate it bound to \`<<camel>>Handler\`, push that name into the existing \`handlers\` array, and add \`<<SCREAMING>>_NOTIFICATION_TASK_TYPE\` to the \`ALL_NOTIFICATION_TASK_TYPES\` aggregate in the component package's \`notification.task.ts\` so the validate-vs-handler parity check stays consistent.`, ctx),
51341
+ description: applyTokens(
51342
+ `Import the new factory, instantiate it bound to \`<<camel>>Handler\`, push that name into the existing \`handlers\` array, and add \`<<SCREAMING>>_NOTIFICATION_TASK_TYPE\` to the \`ALL_NOTIFICATION_TASK_TYPES\` aggregate in the component package's \`notification.task.ts\` so the validate-vs-handler parity check stays consistent.`,
51343
+ ctx
51344
+ ),
50878
51345
  snippet: applyTokens(NOTIFICATION_TASK_WIRING_SNIPPET, ctx)
50879
51346
  }
50880
51347
  ];
@@ -50883,7 +51350,10 @@ function renderNotificationTask(input, ctx) {
50883
51350
  tokens: ctx.tokens,
50884
51351
  files: [componentFile, handlerFile],
50885
51352
  wiring,
50886
- summary: applyTokens(`Scaffolds a new \`<<SCREAMING>>_NOTIFICATION_TASK_TYPE\` end-to-end: component-side type + checkpoint alias + data + template factory (append to \`notification.task.ts\`) and an API-side handler factory in \`handlers/\`. After applying the wiring (handlers array + ALL_NOTIFICATION_TASK_TYPES), run \`dbx_notification_m_validate_app\` and \`dbx_notification_m_validate_folder\` against your project \u2014 both should PASS.`, ctx)
51353
+ summary: applyTokens(
51354
+ `Scaffolds a new \`<<SCREAMING>>_NOTIFICATION_TASK_TYPE\` end-to-end: component-side type + checkpoint alias + data + template factory (append to \`notification.task.ts\`) and an API-side handler factory in \`handlers/\`. After applying the wiring (handlers array + ALL_NOTIFICATION_TASK_TYPES), run \`dbx_notification_m_validate_app\` and \`dbx_notification_m_validate_folder\` against your project \u2014 both should PASS.`,
51355
+ ctx
51356
+ )
50887
51357
  };
50888
51358
  return result;
50889
51359
  }
@@ -51148,7 +51618,11 @@ var STORAGEFILE_PURPOSE = {
51148
51618
  heading: "API upload handler",
51149
51619
  path: "<apiDir>/src/app/common/model/storagefile/handlers/upload.<name>.ts",
51150
51620
  altPaths: ["Inline in `<apiDir>/src/app/common/model/storagefile/storagefile.upload.service.ts` for tiny workspaces."],
51151
- body: ["**Required export:** `<camelName>StorageFileUploadInitializers(context): StorageFileInitializeFromUploadServiceInitializer[]`.", "", "**Wire into:** `<apiDir>/src/app/common/model/storagefile/storagefile.upload.service.ts` \u2014 spread into the `initializer: [...]` array passed to `storageFileInitializeFromUploadService({ initializer })`."].join("\n")
51621
+ body: [
51622
+ "**Required export:** `<camelName>StorageFileUploadInitializers(context): StorageFileInitializeFromUploadServiceInitializer[]`.",
51623
+ "",
51624
+ "**Wire into:** `<apiDir>/src/app/common/model/storagefile/storagefile.upload.service.ts` \u2014 spread into the `initializer: [...]` array passed to `storageFileInitializeFromUploadService({ initializer })`."
51625
+ ].join("\n")
51152
51626
  },
51153
51627
  {
51154
51628
  heading: "API processor (only if subtasks declared)",
@@ -51210,12 +51684,27 @@ var STORAGEFILE_PROCESSOR = {
51210
51684
  {
51211
51685
  heading: "API processor handler",
51212
51686
  path: "<apiDir>/src/app/common/model/notification/handlers/storagefile/task.handler.storagefile.<name>.ts",
51213
- body: ["**Required shape:**", "```ts", "export function <camelName>ProcessingNotificationTaskHandler(context): StorageFileProcessingPurposeSubtaskProcessorConfig<<Name>SubtaskMetadata, <Name>ProcessingSubtask> {", " const config: StorageFileProcessingPurposeSubtaskProcessorConfig<...> = {", " target: <NAME>_PURPOSE,", " flow: [", " { subtask: <NAME>_PURPOSE_<DESC>_SUBTASK, fn: async (input) => ... }", " ]", " };", " return config;", "}", "```"].join("\n")
51687
+ body: [
51688
+ "**Required shape:**",
51689
+ "```ts",
51690
+ "export function <camelName>ProcessingNotificationTaskHandler(context): StorageFileProcessingPurposeSubtaskProcessorConfig<<Name>SubtaskMetadata, <Name>ProcessingSubtask> {",
51691
+ " const config: StorageFileProcessingPurposeSubtaskProcessorConfig<...> = {",
51692
+ " target: <NAME>_PURPOSE,",
51693
+ " flow: [",
51694
+ " { subtask: <NAME>_PURPOSE_<DESC>_SUBTASK, fn: async (input) => ... }",
51695
+ " ]",
51696
+ " };",
51697
+ " return config;",
51698
+ "}",
51699
+ "```"
51700
+ ].join("\n")
51214
51701
  },
51215
51702
  {
51216
51703
  heading: "Wire into processing handler",
51217
51704
  path: "<apiDir>/src/app/common/model/notification/handlers/task.handler.storagefile.ts",
51218
- body: ["Push the new processor config into the `processors: [...]` array passed to `storageFileProcessingNotificationTaskHandler({ processors })`. The surrounding handler is itself wired into `notificationTaskService({ handlers })` \u2014 already covered by the notification-task plumbing, so no further wiring needed here."].join("\n")
51705
+ body: [
51706
+ "Push the new processor config into the `processors: [...]` array passed to `storageFileProcessingNotificationTaskHandler({ processors })`. The surrounding handler is itself wired into `notificationTaskService({ handlers })` \u2014 already covered by the notification-task plumbing, so no further wiring needed here."
51707
+ ].join("\n")
51219
51708
  }
51220
51709
  ],
51221
51710
  seeAlso: ["storagefile-purpose", "storagefile-processor-subtask", "notification-task"],
@@ -51229,7 +51718,12 @@ var STORAGEFILE_PROCESSOR_SUBTASK = {
51229
51718
  {
51230
51719
  heading: "Component declarations",
51231
51720
  path: "<componentDir>/src/lib/model/storagefile/storagefile.<name>.ts (the file that already declares the purpose)",
51232
- body: ["**Add:**", "- `<NAME>_PURPOSE_<DESC>_SUBTASK: StorageFileProcessingSubtask = '<descriptor>'` \u2014 the new subtask constant.", "- Extend the `<Name>ProcessingSubtask` union alias: `typeof <NAME>_PURPOSE_<DESC>_SUBTASK | ...existing`.", "- Optional: extend `<Name>ProcessingSubtaskMetadata` with any new fields the new subtask needs."].join("\n")
51721
+ body: [
51722
+ "**Add:**",
51723
+ "- `<NAME>_PURPOSE_<DESC>_SUBTASK: StorageFileProcessingSubtask = '<descriptor>'` \u2014 the new subtask constant.",
51724
+ "- Extend the `<Name>ProcessingSubtask` union alias: `typeof <NAME>_PURPOSE_<DESC>_SUBTASK | ...existing`.",
51725
+ "- Optional: extend `<Name>ProcessingSubtaskMetadata` with any new fields the new subtask needs."
51726
+ ].join("\n")
51233
51727
  },
51234
51728
  {
51235
51729
  heading: "API processor flow",
@@ -51261,13 +51755,19 @@ var NOTIFICATION_TEMPLATE = {
51261
51755
  {
51262
51756
  heading: "Component aggregator",
51263
51757
  path: "<componentDir>/src/lib/model/notification/notification.ts",
51264
- body: ["Add the new info to the call to `notificationTemplateTypeInfoRecord([...])` \u2014 either as a direct entry (`<NAME>_NOTIFICATION_TEMPLATE_TYPE_INFO`) or via a spread of the module aggregate (`...ALL_<MODULE>_NOTIFICATION_TEMPLATE_TYPE_INFOS`). The result constant is typically `<APP>_FIREBASE_NOTIFICATION_TEMPLATE_TYPE_INFO_RECORD`."].join("\n")
51758
+ body: [
51759
+ "Add the new info to the call to `notificationTemplateTypeInfoRecord([...])` \u2014 either as a direct entry (`<NAME>_NOTIFICATION_TEMPLATE_TYPE_INFO`) or via a spread of the module aggregate (`...ALL_<MODULE>_NOTIFICATION_TEMPLATE_TYPE_INFOS`). The result constant is typically `<APP>_FIREBASE_NOTIFICATION_TEMPLATE_TYPE_INFO_RECORD`."
51760
+ ].join("\n")
51265
51761
  },
51266
51762
  {
51267
51763
  heading: "API handler factory",
51268
51764
  path: "<apiDir>/src/app/common/model/notification/notification.factory.ts",
51269
51765
  altPaths: ["<apiDir>/src/app/common/model/notification/notification.factory.<name>.ts (split per-template for larger workspaces)"],
51270
- body: ["**Required export:** A factory function returning a `NotificationTemplateServiceTypeConfig` with `{ type: <NAME>_NOTIFICATION_TEMPLATE_TYPE, factory: async (...) => ... }`.", "", "**Wire into:** the top-level `<app>NotificationTemplateServiceConfigsArrayFactory(context)` \u2014 call the new factory inside its returned array. The configs-array factory is itself bound via `NOTIFICATION_TEMPLATE_SERVICE_CONFIGS_ARRAY_TOKEN` in the notification module."].join("\n")
51766
+ body: [
51767
+ "**Required export:** A factory function returning a `NotificationTemplateServiceTypeConfig` with `{ type: <NAME>_NOTIFICATION_TEMPLATE_TYPE, factory: async (...) => ... }`.",
51768
+ "",
51769
+ "**Wire into:** the top-level `<app>NotificationTemplateServiceConfigsArrayFactory(context)` \u2014 call the new factory inside its returned array. The configs-array factory is itself bound via `NOTIFICATION_TEMPLATE_SERVICE_CONFIGS_ARRAY_TOKEN` in the notification module."
51770
+ ].join("\n")
51271
51771
  }
51272
51772
  ],
51273
51773
  seeAlso: ["notification-task"],
@@ -51296,7 +51796,17 @@ var NOTIFICATION_TASK = {
51296
51796
  heading: "API handler config",
51297
51797
  path: "<apiDir>/src/app/common/model/notification/notification.task.service.ts",
51298
51798
  altPaths: ["<apiDir>/src/app/common/model/notification/handlers/task.handler.<name>.ts (per-task split for larger workspaces)"],
51299
- body: ["**Required shape:**", "```ts", "const <camelName>NotificationTaskHandler: NotificationTaskServiceTaskHandlerConfig<<Name>NotificationTaskData, <Name>NotificationTaskCheckpoint> = {", " type: <NAME>_NOTIFICATION_TASK_TYPE,", " flow: [", " { checkpoint: 'part_a', fn: async (notificationTask) => ... }", " ]", "};", "```"].join("\n")
51799
+ body: [
51800
+ "**Required shape:**",
51801
+ "```ts",
51802
+ "const <camelName>NotificationTaskHandler: NotificationTaskServiceTaskHandlerConfig<<Name>NotificationTaskData, <Name>NotificationTaskCheckpoint> = {",
51803
+ " type: <NAME>_NOTIFICATION_TASK_TYPE,",
51804
+ " flow: [",
51805
+ " { checkpoint: 'part_a', fn: async (notificationTask) => ... }",
51806
+ " ]",
51807
+ "};",
51808
+ "```"
51809
+ ].join("\n")
51300
51810
  },
51301
51811
  {
51302
51812
  heading: "Wire into task service",
@@ -52219,7 +52729,12 @@ function formatFirestoreRulesScanReportAsMarkdown(report) {
52219
52729
  }
52220
52730
  lines.push("");
52221
52731
  if (report.serverOnly.length > 0) {
52222
- lines.push(`**Server-only (${report.serverOnly.length}):** ${report.serverOnly.map((c) => `\`${c}\``).join(", ")}`, "", "Each of these must carry `@dbxModelServerOnly` on its model interface AND `serverOnly: true` on its `firebaseModelServiceFactory` config, so the model API refuses the read instead of authorizing it via `roleMapForModel`.", "");
52732
+ lines.push(
52733
+ `**Server-only (${report.serverOnly.length}):** ${report.serverOnly.map((c) => `\`${c}\``).join(", ")}`,
52734
+ "",
52735
+ "Each of these must carry `@dbxModelServerOnly` on its model interface AND `serverOnly: true` on its `firebaseModelServiceFactory` config, so the model API refuses the read instead of authorizing it via `roleMapForModel`.",
52736
+ ""
52737
+ );
52223
52738
  }
52224
52739
  if (report.gettableNotListable.length > 0) {
52225
52740
  lines.push(`**Gettable but not listable (${report.gettableNotListable.length}):** ${report.gettableNotListable.map((c) => `\`${c}\``).join(", ")}`, "", "`firestore-get` works on these; `firestore-query` does not \u2014 the rules grant reads by id only.", "");
@@ -52457,7 +52972,15 @@ function validateModelServerOnly(input) {
52457
52972
  }
52458
52973
  for (const iface of input.interfaces) {
52459
52974
  if (iface.serverOnly && !iface.hasModelTag) {
52460
- violations.push(buildViolation({ code: "MODEL_SERVER_ONLY_TAG_WITHOUT_MODEL_TAG" /* MODEL_SERVER_ONLY_TAG_WITHOUT_MODEL_TAG */, severity: "warning", modelType: void 0, file: iface.file, message: `Interface \`${iface.name}\` carries \`@dbxModelServerOnly\` but no \`@dbxModel\` tag, so the model extractor skips it and \`serverOnly\` never lands on the generated model manifest.` }));
52975
+ violations.push(
52976
+ buildViolation({
52977
+ code: "MODEL_SERVER_ONLY_TAG_WITHOUT_MODEL_TAG" /* MODEL_SERVER_ONLY_TAG_WITHOUT_MODEL_TAG */,
52978
+ severity: "warning",
52979
+ modelType: void 0,
52980
+ file: iface.file,
52981
+ message: `Interface \`${iface.name}\` carries \`@dbxModelServerOnly\` but no \`@dbxModel\` tag, so the model extractor skips it and \`serverOnly\` never lands on the generated model manifest.`
52982
+ })
52983
+ );
52461
52984
  }
52462
52985
  }
52463
52986
  const errorCount = violations.filter((v) => v.severity === "error").length;
@@ -52479,22 +53002,78 @@ function appendServiceViolations(input) {
52479
53002
  const { modelType, collection, tag, flag, rules } = reconciliation;
52480
53003
  const declared = tag === true || flag;
52481
53004
  if (collection == null) {
52482
- violations.push(buildViolation({ code: "MODEL_SERVER_ONLY_UNRESOLVED_IDENTITY" /* MODEL_SERVER_ONLY_UNRESOLVED_IDENTITY */, severity: "warning", modelType, file: service.exportName, message: `No \`firestoreModelIdentity(...)\` for model type \`${modelType}\` was found in the scanned dirs, so its collection name \u2014 and therefore the rules leg of the reconciliation \u2014 could not be resolved.` }));
53005
+ violations.push(
53006
+ buildViolation({
53007
+ code: "MODEL_SERVER_ONLY_UNRESOLVED_IDENTITY" /* MODEL_SERVER_ONLY_UNRESOLVED_IDENTITY */,
53008
+ severity: "warning",
53009
+ modelType,
53010
+ file: service.exportName,
53011
+ message: `No \`firestoreModelIdentity(...)\` for model type \`${modelType}\` was found in the scanned dirs, so its collection name \u2014 and therefore the rules leg of the reconciliation \u2014 could not be resolved.`
53012
+ })
53013
+ );
52483
53014
  } else if (rules === true && !flag) {
52484
- violations.push(buildViolation({ code: "MODEL_SERVER_ONLY_MISSING_RUNTIME_FLAG" /* MODEL_SERVER_ONLY_MISSING_RUNTIME_FLAG */, severity: "error", modelType, file: service.exportName, message: `\`${collection}\` has no client read grant in the rules (get=${reconciliation.rulesGet}, list=${reconciliation.rulesList}) but \`${service.exportName}\` does not set \`serverOnly: true\`, so the model API will hand a client a document the rules would refuse.` }));
53015
+ violations.push(
53016
+ buildViolation({
53017
+ code: "MODEL_SERVER_ONLY_MISSING_RUNTIME_FLAG" /* MODEL_SERVER_ONLY_MISSING_RUNTIME_FLAG */,
53018
+ severity: "error",
53019
+ modelType,
53020
+ file: service.exportName,
53021
+ message: `\`${collection}\` has no client read grant in the rules (get=${reconciliation.rulesGet}, list=${reconciliation.rulesList}) but \`${service.exportName}\` does not set \`serverOnly: true\`, so the model API will hand a client a document the rules would refuse.`
53022
+ })
53023
+ );
52485
53024
  } else if (rules === false && declared) {
52486
- violations.push(buildViolation({ code: "MODEL_SERVER_ONLY_RULES_ALLOW_READ" /* MODEL_SERVER_ONLY_RULES_ALLOW_READ */, severity: "error", modelType, file: service.exportName, message: `\`${modelType}\` declares server-only (${describeDeclarations(tag, flag)}) but \`${collection}\` IS client-readable in the rules (get=${reconciliation.rulesGet}, list=${reconciliation.rulesList}).` }));
53025
+ violations.push(
53026
+ buildViolation({
53027
+ code: "MODEL_SERVER_ONLY_RULES_ALLOW_READ" /* MODEL_SERVER_ONLY_RULES_ALLOW_READ */,
53028
+ severity: "error",
53029
+ modelType,
53030
+ file: service.exportName,
53031
+ message: `\`${modelType}\` declares server-only (${describeDeclarations(tag, flag)}) but \`${collection}\` IS client-readable in the rules (get=${reconciliation.rulesGet}, list=${reconciliation.rulesList}).`
53032
+ })
53033
+ );
52487
53034
  }
52488
53035
  if (rules === true && iface != null && !iface.serverOnly) {
52489
- violations.push(buildViolation({ code: "MODEL_SERVER_ONLY_MISSING_TAG" /* MODEL_SERVER_ONLY_MISSING_TAG */, severity: "warning", modelType, file: iface.file, message: `\`${collection}\` has no client read grant in the rules but interface \`${iface.name}\` has no \`@dbxModelServerOnly\` tag, so the CLI cannot refuse the read locally.` }));
53036
+ violations.push(
53037
+ buildViolation({
53038
+ code: "MODEL_SERVER_ONLY_MISSING_TAG" /* MODEL_SERVER_ONLY_MISSING_TAG */,
53039
+ severity: "warning",
53040
+ modelType,
53041
+ file: iface.file,
53042
+ message: `\`${collection}\` has no client read grant in the rules but interface \`${iface.name}\` has no \`@dbxModelServerOnly\` tag, so the CLI cannot refuse the read locally.`
53043
+ })
53044
+ );
52490
53045
  }
52491
53046
  if (iface == null && flag) {
52492
- violations.push(buildViolation({ code: "MODEL_SERVER_ONLY_NO_INTERFACE" /* MODEL_SERVER_ONLY_NO_INTERFACE */, severity: "warning", modelType, file: service.exportName, message: `\`${modelType}\` sets \`serverOnly: true\` but its data type \`${service.modelName ?? "(unknown)"}\` resolves to no interface in the scanned dirs, so there is nothing to carry \`@dbxModelServerOnly\`.` }));
53047
+ violations.push(
53048
+ buildViolation({
53049
+ code: "MODEL_SERVER_ONLY_NO_INTERFACE" /* MODEL_SERVER_ONLY_NO_INTERFACE */,
53050
+ severity: "warning",
53051
+ modelType,
53052
+ file: service.exportName,
53053
+ message: `\`${modelType}\` sets \`serverOnly: true\` but its data type \`${service.modelName ?? "(unknown)"}\` resolves to no interface in the scanned dirs, so there is nothing to carry \`@dbxModelServerOnly\`.`
53054
+ })
53055
+ );
52493
53056
  } else if (iface != null && tag !== flag) {
52494
- violations.push(buildViolation({ code: "MODEL_SERVER_ONLY_TAG_FLAG_MISMATCH" /* MODEL_SERVER_ONLY_TAG_FLAG_MISMATCH */, severity: "error", modelType, file: iface.file, message: `\`${modelType}\`: interface \`${iface.name}\` ${tag === true ? "carries" : "does NOT carry"} \`@dbxModelServerOnly\` while \`${service.exportName}\` ${flag ? "DOES" : "does not"} set \`serverOnly: true\`.` }));
53057
+ violations.push(
53058
+ buildViolation({
53059
+ code: "MODEL_SERVER_ONLY_TAG_FLAG_MISMATCH" /* MODEL_SERVER_ONLY_TAG_FLAG_MISMATCH */,
53060
+ severity: "error",
53061
+ modelType,
53062
+ file: iface.file,
53063
+ message: `\`${modelType}\`: interface \`${iface.name}\` ${tag === true ? "carries" : "does NOT carry"} \`@dbxModelServerOnly\` while \`${service.exportName}\` ${flag ? "DOES" : "does not"} set \`serverOnly: true\`.`
53064
+ })
53065
+ );
52495
53066
  }
52496
53067
  if (manifestModelTypes != null && declared && !manifestModelTypes.has(modelType)) {
52497
- violations.push(buildViolation({ code: "MODEL_SERVER_ONLY_NOT_IN_MANIFEST" /* MODEL_SERVER_ONLY_NOT_IN_MANIFEST */, severity: "warning", modelType, file: manifestFile, message: `\`${modelType}\` is server-only but has no entry in the generated model manifest, so the CLI's local pre-transport refusal cannot fire for it \u2014 the read costs a round-trip to an API that refuses it.` }));
53068
+ violations.push(
53069
+ buildViolation({
53070
+ code: "MODEL_SERVER_ONLY_NOT_IN_MANIFEST" /* MODEL_SERVER_ONLY_NOT_IN_MANIFEST */,
53071
+ severity: "warning",
53072
+ modelType,
53073
+ file: manifestFile,
53074
+ message: `\`${modelType}\` is server-only but has no entry in the generated model manifest, so the CLI's local pre-transport refusal cannot fire for it \u2014 the read costs a round-trip to an API that refuses it.`
53075
+ })
53076
+ );
52498
53077
  }
52499
53078
  }
52500
53079
  function legsAgree(input) {
@@ -52603,7 +53182,6 @@ async function walkTypeScriptFiles(dir) {
52603
53182
  try {
52604
53183
  entries = (await readdir12(dir)).sort();
52605
53184
  } catch {
52606
- return out;
52607
53185
  }
52608
53186
  for (const entry of entries) {
52609
53187
  if (entry === "node_modules" || entry === "dist") continue;
@@ -52884,7 +53462,7 @@ function relativeOrAbsolute(from, target) {
52884
53462
  const rel = relative20(from, target);
52885
53463
  if (rel.length === 0) return ".";
52886
53464
  if (rel.startsWith("..")) return target;
52887
- return rel.split("\\").join("/");
53465
+ return rel.replaceAll("\\", "/");
52888
53466
  }
52889
53467
 
52890
53468
  // packages/dbx-components-mcp/src/tools/mcp-config/init.ts
@@ -53013,19 +53591,19 @@ async function planRootConfig(input) {
53013
53591
  }
53014
53592
  function defaultOutFor(input) {
53015
53593
  const targetAbs = resolve66(input.snapshot.workspaceRoot, ".tmp/dbx-mcp", `${input.slug}.${clusterFilenameSegment(input.cluster)}.json`);
53016
- return relative21(dirname22(input.scanConfigPath), targetAbs).split("\\").join("/");
53594
+ return relative21(dirname22(input.scanConfigPath), targetAbs).replaceAll("\\", "/");
53017
53595
  }
53018
53596
  function resolveScanOut(scanConfigPath, out) {
53019
53597
  return resolve66(dirname22(scanConfigPath), out);
53020
53598
  }
53021
53599
  function relativeFromConfig(snapshot, absolute) {
53022
53600
  const configDir = snapshot.configPath === null ? snapshot.workspaceRoot : dirname22(snapshot.configPath);
53023
- return relative21(configDir, absolute).split("\\").join("/");
53601
+ return relative21(configDir, absolute).replaceAll("\\", "/");
53024
53602
  }
53025
53603
  function workspaceRel(workspaceRoot, absolute) {
53026
53604
  const rel = relative21(workspaceRoot, absolute);
53027
53605
  if (rel.length === 0) return ".";
53028
- return rel.split("\\").join("/");
53606
+ return rel.replaceAll("\\", "/");
53029
53607
  }
53030
53608
  function serializeJson(value) {
53031
53609
  return `${JSON.stringify(value, null, 2)}
@@ -55618,7 +56196,13 @@ async function tryReadBatchPath(path, cwd, reader) {
55618
56196
  import { type as type104 } from "arktype";
55619
56197
  var DBX_AUTH_CLAIM_LOOKUP_TOOL = {
55620
56198
  name: "dbx_auth_claim_lookup",
55621
- description: ["Look up a Firebase Auth custom-claim entry by key (`a`, `o`, `fr`) or by `*ApiAuthClaims` interface name (`DemoApiAuthClaims`).", "", "Returns the claim meaning, value type, role mapping through `authRoleClaimsService`, owning app + interface, and the source path:line where the claim is declared.", "", "Use the optional `app` parameter (`demo-api`) to scope the lookup when the same claim key exists in multiple apps."].join("\n"),
56199
+ description: [
56200
+ "Look up a Firebase Auth custom-claim entry by key (`a`, `o`, `fr`) or by `*ApiAuthClaims` interface name (`DemoApiAuthClaims`).",
56201
+ "",
56202
+ "Returns the claim meaning, value type, role mapping through `authRoleClaimsService`, owning app + interface, and the source path:line where the claim is declared.",
56203
+ "",
56204
+ "Use the optional `app` parameter (`demo-api`) to scope the lookup when the same claim key exists in multiple apps."
56205
+ ].join("\n"),
55622
56206
  inputSchema: {
55623
56207
  type: "object",
55624
56208
  properties: {
@@ -55841,7 +56425,14 @@ function sourceLink2(path, line) {
55841
56425
  import { type as type106 } from "arktype";
55842
56426
  var DBX_AUTH_ROLE_LOOKUP_TOOL = {
55843
56427
  name: "dbx_auth_role_lookup",
55844
- description: ["Look up auth roles forward (by role string / const name), by tag, or in reverse (model + verb \u2192 required roles/scopes).", "", '- `topic="admin"` or `topic="AUTH_ADMIN_ROLE"` \u2192 role entry + claims that set it.', '- `tag="privileged"` \u2192 every role tagged with `privileged`.', '- `model="StorageFile" verb="read"` \u2192 reverse lookup of the roles/scopes the gate would expect.', '- `topic="list"` \u2192 catalog.'].join("\n"),
56428
+ description: [
56429
+ "Look up auth roles forward (by role string / const name), by tag, or in reverse (model + verb \u2192 required roles/scopes).",
56430
+ "",
56431
+ '- `topic="admin"` or `topic="AUTH_ADMIN_ROLE"` \u2192 role entry + claims that set it.',
56432
+ '- `tag="privileged"` \u2192 every role tagged with `privileged`.',
56433
+ '- `model="StorageFile" verb="read"` \u2192 reverse lookup of the roles/scopes the gate would expect.',
56434
+ '- `topic="list"` \u2192 catalog.'
56435
+ ].join("\n"),
55845
56436
  inputSchema: {
55846
56437
  type: "object",
55847
56438
  properties: {
@@ -56024,7 +56615,11 @@ function sourceLink3(path, line) {
56024
56615
  import { type as type107 } from "arktype";
56025
56616
  var DBX_AUTH_TOKEN_EXPLAIN_TOOL = {
56026
56617
  name: "dbx_auth_token_explain",
56027
- description: ["Decode a JWT or claims object and annotate each claim with the catalogued meaning, role mapping, and source path:line.", "", "Pass `token` (raw JWT string) **or** `claims` (decoded JSON object). Reserved JWT claims (`iss`/`aud`/`exp`/`iat`/`sub`/`scope`) are listed separately. Flags expired tokens and unknown wrong-issuer claims."].join("\n"),
56618
+ description: [
56619
+ "Decode a JWT or claims object and annotate each claim with the catalogued meaning, role mapping, and source path:line.",
56620
+ "",
56621
+ "Pass `token` (raw JWT string) **or** `claims` (decoded JSON object). Reserved JWT claims (`iss`/`aud`/`exp`/`iat`/`sub`/`scope`) are listed separately. Flags expired tokens and unknown wrong-issuer claims."
56622
+ ].join("\n"),
56028
56623
  inputSchema: {
56029
56624
  type: "object",
56030
56625
  properties: {
@@ -56483,7 +57078,13 @@ function createAuthClusterTools(registry) {
56483
57078
  function registerTools(server, options = {}) {
56484
57079
  const underlyingServer = server.server;
56485
57080
  const tools = [...DBX_TOOLS];
56486
- tools.push(createUiExamplesTool({ examplesRegistry: options.dbxDocsUiExamplesRegistry }), createModelValidateTool({ ruleOptions: options.modelValidateRuleOptions }), createModelValidateFolderTool({ ruleOptions: options.modelValidateRuleOptions }), createModelFixtureValidateAppTool({ getRegistry: () => options.fixtureModelRegistry }), createLogSearchTool(options.logSearchConfig));
57081
+ tools.push(
57082
+ createUiExamplesTool({ examplesRegistry: options.dbxDocsUiExamplesRegistry }),
57083
+ createModelValidateTool({ ruleOptions: options.modelValidateRuleOptions }),
57084
+ createModelValidateFolderTool({ ruleOptions: options.modelValidateRuleOptions }),
57085
+ createModelFixtureValidateAppTool({ getRegistry: () => options.fixtureModelRegistry }),
57086
+ createLogSearchTool(options.logSearchConfig)
57087
+ );
56487
57088
  if (options.forgeFieldRegistry !== void 0) {
56488
57089
  tools.push(createLookupFormTool({ registry: options.forgeFieldRegistry }), createSearchFormTool({ registry: options.forgeFieldRegistry }), createFormScaffoldTool({ registry: options.forgeFieldRegistry }));
56489
57090
  }
@@ -56737,7 +57338,25 @@ async function loadServerContext(options = {}) {
56737
57338
  await emitDownstreamHints({ cwd, externalCounts, onDownstreamHints: options.onDownstreamHints });
56738
57339
  const context = {
56739
57340
  resources: { semanticTypeRegistry: registry, forgeFieldRegistry: forgeRegistry, pipeRegistry, utilRegistry, modelSnapshotFieldRegistry, modelFirebaseIndexRegistry, uiComponentRegistry: uiRegistry, actionRegistry, filterRegistry, tokenRegistry, cssUtilityRegistry, authRegistry, cwd },
56740
- tools: { semanticTypeRegistry: registry, forgeFieldRegistry: forgeRegistry, pipeRegistry, utilRegistry, modelSnapshotFieldRegistry, modelFirebaseIndexRegistry, uiComponentRegistry: uiRegistry, dbxDocsUiExamplesRegistry, actionRegistry, filterRegistry, tokenRegistry, cssUtilityRegistry, fixtureModelRegistry, modelValidateRuleOptions, authRegistry, cwd, logSearchConfig }
57341
+ tools: {
57342
+ semanticTypeRegistry: registry,
57343
+ forgeFieldRegistry: forgeRegistry,
57344
+ pipeRegistry,
57345
+ utilRegistry,
57346
+ modelSnapshotFieldRegistry,
57347
+ modelFirebaseIndexRegistry,
57348
+ uiComponentRegistry: uiRegistry,
57349
+ dbxDocsUiExamplesRegistry,
57350
+ actionRegistry,
57351
+ filterRegistry,
57352
+ tokenRegistry,
57353
+ cssUtilityRegistry,
57354
+ fixtureModelRegistry,
57355
+ modelValidateRuleOptions,
57356
+ authRegistry,
57357
+ cwd,
57358
+ logSearchConfig
57359
+ }
56741
57360
  };
56742
57361
  return context;
56743
57362
  }
@@ -56841,7 +57460,14 @@ async function resolveAuthRegistry(input) {
56841
57460
  }
56842
57461
  function reportAuthLoaderResult(result) {
56843
57462
  const { registry, scannedFiles, fileWarnings, extractWarnings, extractedAppCount, extractedClaimCount } = result;
56844
- const summary = [`[dbx-components-mcp] auth registry loaded`, ` sources: ${registry.loadedSources.join(", ") || "(none)"}`, ` scanned-files: ${scannedFiles.length}`, ` apps: ${registry.apps.length} (extracted: ${extractedAppCount})`, ` claims: ${registry.claims.length} (extracted: ${extractedClaimCount})`, ` warnings: ${fileWarnings.length + extractWarnings.length}`].join("\n");
57463
+ const summary = [
57464
+ `[dbx-components-mcp] auth registry loaded`,
57465
+ ` sources: ${registry.loadedSources.join(", ") || "(none)"}`,
57466
+ ` scanned-files: ${scannedFiles.length}`,
57467
+ ` apps: ${registry.apps.length} (extracted: ${extractedAppCount})`,
57468
+ ` claims: ${registry.claims.length} (extracted: ${extractedClaimCount})`,
57469
+ ` warnings: ${fileWarnings.length + extractWarnings.length}`
57470
+ ].join("\n");
56845
57471
  process.stderr.write(`${summary}
56846
57472
  `);
56847
57473
  for (const warning of fileWarnings) {