@dereekb/dbx-components-mcp 14.0.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.
package/README.md CHANGED
@@ -78,4 +78,4 @@ The Firebase model registry (`src/registry/firebase-models.generated.ts`) is pro
78
78
  npx nx run dbx-components-mcp:generate-firebase-models
79
79
  ```
80
80
 
81
- The script formats output through Prettier so regenerating produces a byte-equal file when source models haven't changed.
81
+ The script formats output through oxfmt so regenerating produces a byte-equal file when source models haven't changed.
@@ -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: "14.0.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": "14.0.0",
25
- "@dereekb/util": "14.0.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
  }
@@ -10134,7 +10134,7 @@ function collectFromTypeReference(ref, context) {
10134
10134
  result = collectFromTypeNode(args[0], { ...context, forceOptional: false }).map((p) => ({ ...p, required: true }));
10135
10135
  } else if (refName === "Omit" && args.length === 2) {
10136
10136
  const keys = parseKeyUnion(args[1]);
10137
- const merged = /* @__PURE__ */ new Set([...context.omit, ...keys]);
10137
+ const merged = /* @__PURE__ */ new Set([...Array.from(context.omit), ...Array.from(keys)]);
10138
10138
  result = collectFromTypeNode(args[0], { ...context, omit: merged });
10139
10139
  } else if (refName === "Pick" && args.length === 2) {
10140
10140
  const keys = parseKeyUnion(args[1]);
@@ -11476,7 +11476,7 @@ function tryTransitiveResolution(input) {
11476
11476
  const calleeLine = resolved.decl.getStartLineNumber();
11477
11477
  warnings.push({ kind: "unannotated-query-helper", severity: "warning", name: factoryName, callee: calleeName, calleeFilePath, calleeLine, filePath, line: call.getStartLineNumber() });
11478
11478
  }
11479
- const nextVisited = /* @__PURE__ */ new Set([...visited, calleeKey]);
11479
+ const nextVisited = new Set(visited).add(calleeKey);
11480
11480
  walkBodyInto({
11481
11481
  decl: resolved.decl,
11482
11482
  factoryName,
@@ -15456,13 +15456,21 @@ function formatBuildFailure(outcome) {
15456
15456
  }
15457
15457
  function formatWriteSummary(input) {
15458
15458
  const { outputPath, diff, generatedComposites, generatedFieldOverrides } = input;
15459
- 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
+ ];
15460
15464
  return appendDiffSection(lines, diff).join("\n");
15461
15465
  }
15462
15466
  function formatCheckSummary(input) {
15463
15467
  const { drift, outputPath, diff, generatedComposites, generatedFieldOverrides } = input;
15464
15468
  const headline = drift ? `generate-firestore-indexes: drift detected against ${outputPath}` : `generate-firestore-indexes: in sync (${outputPath})`;
15465
- 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
+ ];
15466
15474
  return appendDiffSection(lines, diff).join("\n");
15467
15475
  }
15468
15476
  function appendDiffSection(lines, diff) {
@@ -19816,7 +19824,7 @@ function registerResources(server, options = {}) {
19816
19824
  if (options.authRegistry !== void 0) {
19817
19825
  registerAuthResource(server, { registry: options.authRegistry });
19818
19826
  }
19819
- registerFirestoreRulesResource(server, { ...options.cwd === void 0 ? {} : { cwd: options.cwd } });
19827
+ registerFirestoreRulesResource(server, options.cwd === void 0 ? {} : { cwd: options.cwd });
19820
19828
  }
19821
19829
 
19822
19830
  // packages/dbx-components-mcp/src/tools/lookup-form.tool.ts
@@ -19909,7 +19917,19 @@ function formatBrief(field) {
19909
19917
  const tierBits = formatTierBits(field);
19910
19918
  const arrayOptional = field.arrayOutput === "optional" ? " (single or array)" : "";
19911
19919
  const array = field.arrayOutput === "yes" ? " (array)" : arrayOptional;
19912
- 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");
19913
19933
  }
19914
19934
  function formatFull(field) {
19915
19935
  const header = formatHeader(field);
@@ -20411,7 +20431,19 @@ function formatSearchResults(input) {
20411
20431
  const field = hit.entry;
20412
20432
  const optionalArray = field.arrayOutput === "optional" ? " *(single or array)*" : "";
20413
20433
  const array = field.arrayOutput === "yes" ? " *(array)*" : optionalArray;
20414
- 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
+ );
20415
20447
  }
20416
20448
  return lines.join("\n").trimEnd();
20417
20449
  }
@@ -21463,7 +21495,19 @@ function formatSearchResults2(options) {
21463
21495
  } else {
21464
21496
  const lines = [`# Search: \`${query}\``, "", `Tokens: \`${tokenDisplay}\`${scopeLabel} \xB7 ${hits.length} result${hits.length === 1 ? "" : "s"}`, ""];
21465
21497
  for (const hit of hits) {
21466
- 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
+ );
21467
21511
  }
21468
21512
  appendRelatedExamples(lines, hits, examplesRegistry);
21469
21513
  result = lines.join("\n").trimEnd();
@@ -25553,7 +25597,31 @@ function attachRemediation(code17) {
25553
25597
 
25554
25598
  // packages/dbx-cli/validate/src/lib/_core/rule-catalog/format.ts
25555
25599
  function formatRuleAsMarkdown(entry) {
25556
- 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
+ ];
25557
25625
  if (entry.canonicalFixTemplate) {
25558
25626
  lines.push("## Fix template", "", entry.canonicalFixTemplate, "");
25559
25627
  }
@@ -35894,7 +35962,15 @@ function appendGroupSection(lines, apiRel, group) {
35894
35962
  function appendFileTable(lines, group) {
35895
35963
  lines.push("", "| File | Kind | Subgroups | Canonical | Rename suggestion |", "|---|---|---|---|---|");
35896
35964
  for (const file of group.files) {
35897
- 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
+ );
35898
35974
  }
35899
35975
  }
35900
35976
  function appendWhereToAddSection(lines, apiRel, group) {
@@ -35910,7 +35986,17 @@ function appendRecommendation(lines, rec, group) {
35910
35986
  lines.push("", `- **${rec.label}** \u2192 \`${rec.canonicalPath}\` ${status}`, ` ${rec.summary}`);
35911
35987
  }
35912
35988
  function appendConventionLegend(lines) {
35913
- 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
+ );
35914
36000
  }
35915
36001
  function appendDriftSummary(lines, catalog) {
35916
36002
  const driftFiles = [];
@@ -36056,7 +36142,14 @@ function stringify(value) {
36056
36142
 
36057
36143
  // packages/dbx-cli/model-test/src/lib/fixture/format.markdown.ts
36058
36144
  function formatListAsMarkdown(extraction) {
36059
- 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
+ ];
36060
36153
  if (extraction.entries.length === 0) {
36061
36154
  lines.push("", "_No model fixtures found._");
36062
36155
  } else {
@@ -36113,7 +36206,15 @@ function nonModelDescription(entry) {
36113
36206
  return "Non-Firestore-model fixture.";
36114
36207
  }
36115
36208
  function appendLookupTriplet(lines, entry) {
36116
- 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
+ );
36117
36218
  }
36118
36219
  function appendLookupGenerics(lines, entry) {
36119
36220
  if (entry.fixtureExtendsGenerics.length > 0) {
@@ -36283,11 +36384,19 @@ function renderParamsBlock(input, paramsTypeName, todos) {
36283
36384
  }
36284
36385
  function renderInstanceBlock(input) {
36285
36386
  input.todos.push(`Add instance methods to \`${input.instanceClassName}\`.`);
36286
- 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");
36287
36392
  }
36288
36393
  function renderFixtureBlock(input) {
36289
36394
  input.todos.push(`Add forwarder methods to \`${input.fixtureClassName}\` after defining instance methods.`);
36290
- 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");
36291
36400
  }
36292
36401
  function renderFactoryBlock(input) {
36293
36402
  const generics = renderFactoryGenerics(input);
@@ -36307,7 +36416,15 @@ function renderFactoryBlock(input) {
36307
36416
  return lines.join("\n");
36308
36417
  }
36309
36418
  function renderFactoryGenerics(input) {
36310
- 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
+ ];
36311
36428
  if (input.collectionGenericArg) {
36312
36429
  return [...baseGenerics, input.collectionGenericArg];
36313
36430
  }
@@ -36329,10 +36446,13 @@ function renderFactoryCallbacks(input) {
36329
36446
  } else {
36330
36447
  callbacks.push(`getCollection: (fi) => fi.${defaultCollectionsAccessor(input.prefix)}.${camelInitial(input.model)}Collection`);
36331
36448
  }
36332
- 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) => {
36333
36452
  // TODO: choose the correct ref strategy (auto id vs deterministic id).
36334
36453
  return collection.documentAccessor().newDocument().documentRef;
36335
- }`);
36454
+ }`
36455
+ );
36336
36456
  if (input.withInitDocument) {
36337
36457
  callbacks.push(`initDocument: async (instance, params) => {
36338
36458
  // TODO: seed the new document with sensible defaults from \`params\`.
@@ -37532,7 +37652,18 @@ function formatReportAsJson4(report) {
37532
37652
  // packages/dbx-cli/validate/src/lib/dbx-asset-list-app/format.markdown.ts
37533
37653
  function formatReportAsMarkdown4(report) {
37534
37654
  const basename12 = report.componentDir.split("/").pop() ?? report.componentDir;
37535
- 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
+ ];
37536
37667
  if (report.assets.length === 0) {
37537
37668
  lines.push("", "_None found._");
37538
37669
  } else {
@@ -37923,7 +38054,18 @@ function formatReportAsJson6(report) {
37923
38054
  function formatReportAsMarkdown6(report) {
37924
38055
  const basename12 = report.componentDir.split("/").pop() ?? report.componentDir;
37925
38056
  const uploadServiceFactoryText = report.uploadServiceFactoryName ? code7(report.uploadServiceFactoryName) : "_Not defined._";
37926
- 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
+ ];
37927
38069
  if (report.purposes.length === 0) {
37928
38070
  lines.push("", "_None found._");
37929
38071
  } else {
@@ -41602,7 +41744,14 @@ function rootDocumentStoreBody(args) {
41602
41744
  if (args.functionsClass !== void 0) {
41603
41745
  packageImports.push(args.functionsClass);
41604
41746
  }
41605
- 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
+ ];
41606
41755
  if (args.functionsClass !== void 0) {
41607
41756
  lines.push(` readonly ${functionsFieldName(args.functionsClass)} = inject(${args.functionsClass});`, "");
41608
41757
  }
@@ -41645,11 +41794,29 @@ function subDocumentStoreBody(args) {
41645
41794
  if (args.functionsClass !== void 0) {
41646
41795
  packageImports.push(args.functionsClass);
41647
41796
  }
41648
- 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
+ ];
41649
41806
  if (args.functionsClass !== void 0) {
41650
41807
  lines.push(` readonly ${functionsFieldName(args.functionsClass)} = inject(${args.functionsClass});`, "");
41651
41808
  }
41652
- 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
+ );
41653
41820
  const crudLines = renderCrudLines(args, "this");
41654
41821
  if (crudLines.length > 0) {
41655
41822
  lines.push("", ...crudLines);
@@ -41694,7 +41861,14 @@ function systemStateAccessorBody(args) {
41694
41861
  if (args.functionsClass !== void 0) {
41695
41862
  packageImports.push(args.functionsClass);
41696
41863
  }
41697
- 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
+ ];
41698
41872
  if (args.functionsClass !== void 0) {
41699
41873
  lines.push(` readonly ${functionsFieldName(args.functionsClass)} = inject(${args.functionsClass});`, "");
41700
41874
  }
@@ -42277,7 +42451,17 @@ var ForwardArgsType = type62({
42277
42451
  });
42278
42452
  var TOOL8 = {
42279
42453
  name: "dbx_model_fixture_forward",
42280
- 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"),
42281
42465
  inputSchema: {
42282
42466
  type: "object",
42283
42467
  properties: {
@@ -42756,7 +42940,11 @@ function bucketSummary(group, bucket) {
42756
42940
  }
42757
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`).";
42758
42942
  }
42759
- 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
+ ];
42760
42948
  function formatMarkdown(report) {
42761
42949
  const lines = [];
42762
42950
  lines.push(`# Spec convention for \`${report.group}\``, "", `Tests live under \`${report.apiDir}/src/app/function/${report.group}/\`. Canonical name segments:`, "");
@@ -43536,7 +43724,14 @@ var ARCHETYPE_RECOMMEND_TOOL = {
43536
43724
  import { type as type70 } from "arktype";
43537
43725
  var DBX_MODEL_ARCHETYPE_LOOKUP_TOOL = {
43538
43726
  name: "dbx_model_archetype_lookup",
43539
- 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"),
43540
43735
  inputSchema: {
43541
43736
  type: "object",
43542
43737
  properties: {
@@ -44929,7 +45124,18 @@ function formatStateInteraction(entry) {
44929
45124
  return result;
44930
45125
  }
44931
45126
  function formatDirectiveEntry(entry, depth) {
44932
- 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
+ ];
44933
45139
  if (depth === "full") {
44934
45140
  lines.push("## Inputs", "", entry.inputs.length > 0 ? formatInputsTable2(entry.inputs) : "_No inputs._", "");
44935
45141
  if (entry.outputs.length > 0) {
@@ -45692,7 +45898,15 @@ export class TagEditorComponent {
45692
45898
  };
45693
45899
 
45694
45900
  // packages/dbx-components-mcp/src/tools/data/patterns/action-patterns.ts
45695
- 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
+ ];
45696
45910
  function getActionExamplePattern(slug) {
45697
45911
  const lowered = slug.trim().toLowerCase();
45698
45912
  return ACTION_EXAMPLE_PATTERNS.find((p) => p.slug === lowered);
@@ -46016,7 +46230,27 @@ function renderScaffold3(args) {
46016
46230
  const directivesUsed = buildDirectivesUsed(args);
46017
46231
  const examplePattern = relatedExamplePattern(args);
46018
46232
  const lines = [];
46019
- 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
+ );
46020
46254
  for (const line of imports) {
46021
46255
  lines.push(line);
46022
46256
  }
@@ -46460,7 +46694,13 @@ function formatMissingRouteModelLine(param) {
46460
46694
  // packages/dbx-components-mcp/src/tools/route-lookup.tool.ts
46461
46695
  var DBX_ROUTE_LOOKUP_TOOL = {
46462
46696
  name: "dbx_route_lookup",
46463
- 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"),
46464
46704
  inputSchema: {
46465
46705
  type: "object",
46466
46706
  properties: {
@@ -46720,7 +46960,11 @@ var ROUTE_LOOKUP_TOOL = {
46720
46960
  import { type as type82 } from "arktype";
46721
46961
  var DBX_ROUTE_SEARCH_TOOL = {
46722
46962
  name: "dbx_route_search",
46723
- 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"),
46724
46968
  inputSchema: {
46725
46969
  type: "object",
46726
46970
  properties: {
@@ -47812,7 +48056,25 @@ function formatScaffold2(args) {
47812
48056
  if (args.presetKeys.length > 0) {
47813
48057
  lines.push("## Presets", "", "```ts", renderPresetConstants(args), "```", "");
47814
48058
  }
47815
- 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
+ );
47816
48078
  if (args.presetKeys.length > 0) {
47817
48079
  lines.push("- Each `ClickableFilterPreset` listed in the preset array becomes a chip; clicking sets `presetValue` on the filter source.");
47818
48080
  }
@@ -47879,7 +48141,20 @@ function formatArgsTable(args) {
47879
48141
  return lines.join("\n");
47880
48142
  }
47881
48143
  function formatEntry4(entry, depth) {
47882
- 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
+ ];
47883
48158
  if (depth === "full") {
47884
48159
  if (entry.args.length > 0) {
47885
48160
  lines.push("## Args", "", formatArgsTable(entry.args), "");
@@ -48041,7 +48316,20 @@ function formatSearchResults5(input) {
48041
48316
  const lines = [`# Search: \`${query}\``, "", `Tokens: \`${tokenDisplay}\` \xB7 ${hits.length} result${hits.length === 1 ? "" : "s"}`, ""];
48042
48317
  for (const hit of hits) {
48043
48318
  const entry = hit.entry;
48044
- 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
+ );
48045
48333
  }
48046
48334
  return lines.join("\n").trimEnd();
48047
48335
  }
@@ -48317,7 +48605,20 @@ function formatCatalogHit(input) {
48317
48605
  const tagDisplay = entry.tags.length > 0 ? `
48318
48606
  - **tags:** ${tagBadges}` : "";
48319
48607
  const badge = options.headerBadge ?? "";
48320
- 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
+ ];
48321
48622
  }
48322
48623
  function formatCatalogSearchResults(input) {
48323
48624
  const { query, tokens, hits, filters, entityLabel, emptyHint, hitOptions } = input;
@@ -48493,7 +48794,20 @@ function appendFullEntrySections2(lines, entry) {
48493
48794
  }
48494
48795
  function formatEntry6(entry, depth) {
48495
48796
  const heading = `# ${entry.name}`;
48496
- 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
+ ];
48497
48811
  appendDeprecationNotice2(lines, entry);
48498
48812
  if (depth === "full") {
48499
48813
  appendFullEntrySections2(lines, entry);
@@ -49823,7 +50137,14 @@ function appendTaggedSections(lines, tagged) {
49823
50137
  }
49824
50138
  function appendUntaggedSection(lines, untagged) {
49825
50139
  if (untagged.length === 0) return;
49826
- 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
+ );
49827
50148
  for (const candidate of untagged) {
49828
50149
  lines.push(`| \`${candidate.name}\` | \`${candidate.subpath}:${candidate.line}\` |`);
49829
50150
  }
@@ -50523,7 +50844,14 @@ function formatReportAsMarkdown17(report) {
50523
50844
  } else {
50524
50845
  lines.push("## \u2705 In sync", "");
50525
50846
  }
50526
- 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
+ );
50527
50855
  if (report.violations.length > 0) {
50528
50856
  appendViolationSection(
50529
50857
  lines,
@@ -50941,7 +51269,10 @@ function renderStorageFilePurpose(input, ctx) {
50941
51269
  tokens: ctx.tokens,
50942
51270
  files: [componentFile, handlerFile],
50943
51271
  wiring,
50944
- 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
+ )
50945
51276
  };
50946
51277
  return result;
50947
51278
  }
@@ -50949,19 +51280,28 @@ function renderNotificationTemplate(input, ctx) {
50949
51280
  const componentFile = {
50950
51281
  status: "append",
50951
51282
  path: applyTokens("<<componentDir>>/src/lib/model/notification/notification.ts", ctx),
50952
- 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
+ ),
50953
51287
  content: applyTokens(NOTIFICATION_TEMPLATE_COMPONENT_TEMPLATE, ctx)
50954
51288
  };
50955
51289
  const factoryFile = {
50956
51290
  status: "append",
50957
51291
  path: applyTokens("<<apiDir>>/src/app/common/model/notification/notification.factory.ts", ctx),
50958
- 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
+ ),
50959
51296
  content: applyTokens(NOTIFICATION_TEMPLATE_FACTORY_TEMPLATE, ctx)
50960
51297
  };
50961
51298
  const wiring = [
50962
51299
  {
50963
51300
  file: applyTokens("<<apiDir>>/src/app/common/model/notification/notification.factory.ts", ctx),
50964
- 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
+ ),
50965
51305
  snippet: applyTokens(NOTIFICATION_TEMPLATE_WIRING_SNIPPET, ctx)
50966
51306
  }
50967
51307
  ];
@@ -50970,7 +51310,10 @@ function renderNotificationTemplate(input, ctx) {
50970
51310
  tokens: ctx.tokens,
50971
51311
  files: [componentFile, factoryFile],
50972
51312
  wiring,
50973
- 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
+ )
50974
51317
  };
50975
51318
  return result;
50976
51319
  }
@@ -50995,7 +51338,10 @@ function renderNotificationTask(input, ctx) {
50995
51338
  const wiring = [
50996
51339
  {
50997
51340
  file: applyTokens("<<apiDir>>/src/app/common/model/notification/notification.task.service.ts", ctx),
50998
- 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
+ ),
50999
51345
  snippet: applyTokens(NOTIFICATION_TASK_WIRING_SNIPPET, ctx)
51000
51346
  }
51001
51347
  ];
@@ -51004,7 +51350,10 @@ function renderNotificationTask(input, ctx) {
51004
51350
  tokens: ctx.tokens,
51005
51351
  files: [componentFile, handlerFile],
51006
51352
  wiring,
51007
- 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
+ )
51008
51357
  };
51009
51358
  return result;
51010
51359
  }
@@ -51269,7 +51618,11 @@ var STORAGEFILE_PURPOSE = {
51269
51618
  heading: "API upload handler",
51270
51619
  path: "<apiDir>/src/app/common/model/storagefile/handlers/upload.<name>.ts",
51271
51620
  altPaths: ["Inline in `<apiDir>/src/app/common/model/storagefile/storagefile.upload.service.ts` for tiny workspaces."],
51272
- 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")
51273
51626
  },
51274
51627
  {
51275
51628
  heading: "API processor (only if subtasks declared)",
@@ -51331,12 +51684,27 @@ var STORAGEFILE_PROCESSOR = {
51331
51684
  {
51332
51685
  heading: "API processor handler",
51333
51686
  path: "<apiDir>/src/app/common/model/notification/handlers/storagefile/task.handler.storagefile.<name>.ts",
51334
- 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")
51335
51701
  },
51336
51702
  {
51337
51703
  heading: "Wire into processing handler",
51338
51704
  path: "<apiDir>/src/app/common/model/notification/handlers/task.handler.storagefile.ts",
51339
- 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")
51340
51708
  }
51341
51709
  ],
51342
51710
  seeAlso: ["storagefile-purpose", "storagefile-processor-subtask", "notification-task"],
@@ -51350,7 +51718,12 @@ var STORAGEFILE_PROCESSOR_SUBTASK = {
51350
51718
  {
51351
51719
  heading: "Component declarations",
51352
51720
  path: "<componentDir>/src/lib/model/storagefile/storagefile.<name>.ts (the file that already declares the purpose)",
51353
- 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")
51354
51727
  },
51355
51728
  {
51356
51729
  heading: "API processor flow",
@@ -51382,13 +51755,19 @@ var NOTIFICATION_TEMPLATE = {
51382
51755
  {
51383
51756
  heading: "Component aggregator",
51384
51757
  path: "<componentDir>/src/lib/model/notification/notification.ts",
51385
- 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")
51386
51761
  },
51387
51762
  {
51388
51763
  heading: "API handler factory",
51389
51764
  path: "<apiDir>/src/app/common/model/notification/notification.factory.ts",
51390
51765
  altPaths: ["<apiDir>/src/app/common/model/notification/notification.factory.<name>.ts (split per-template for larger workspaces)"],
51391
- 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")
51392
51771
  }
51393
51772
  ],
51394
51773
  seeAlso: ["notification-task"],
@@ -51417,7 +51796,17 @@ var NOTIFICATION_TASK = {
51417
51796
  heading: "API handler config",
51418
51797
  path: "<apiDir>/src/app/common/model/notification/notification.task.service.ts",
51419
51798
  altPaths: ["<apiDir>/src/app/common/model/notification/handlers/task.handler.<name>.ts (per-task split for larger workspaces)"],
51420
- 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")
51421
51810
  },
51422
51811
  {
51423
51812
  heading: "Wire into task service",
@@ -52340,7 +52729,12 @@ function formatFirestoreRulesScanReportAsMarkdown(report) {
52340
52729
  }
52341
52730
  lines.push("");
52342
52731
  if (report.serverOnly.length > 0) {
52343
- 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
+ );
52344
52738
  }
52345
52739
  if (report.gettableNotListable.length > 0) {
52346
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.", "");
@@ -52578,7 +52972,15 @@ function validateModelServerOnly(input) {
52578
52972
  }
52579
52973
  for (const iface of input.interfaces) {
52580
52974
  if (iface.serverOnly && !iface.hasModelTag) {
52581
- 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
+ );
52582
52984
  }
52583
52985
  }
52584
52986
  const errorCount = violations.filter((v) => v.severity === "error").length;
@@ -52600,22 +53002,78 @@ function appendServiceViolations(input) {
52600
53002
  const { modelType, collection, tag, flag, rules } = reconciliation;
52601
53003
  const declared = tag === true || flag;
52602
53004
  if (collection == null) {
52603
- 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
+ );
52604
53014
  } else if (rules === true && !flag) {
52605
- 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
+ );
52606
53024
  } else if (rules === false && declared) {
52607
- 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
+ );
52608
53034
  }
52609
53035
  if (rules === true && iface != null && !iface.serverOnly) {
52610
- 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
+ );
52611
53045
  }
52612
53046
  if (iface == null && flag) {
52613
- 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
+ );
52614
53056
  } else if (iface != null && tag !== flag) {
52615
- 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
+ );
52616
53066
  }
52617
53067
  if (manifestModelTypes != null && declared && !manifestModelTypes.has(modelType)) {
52618
- 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
+ );
52619
53077
  }
52620
53078
  }
52621
53079
  function legsAgree(input) {
@@ -52724,7 +53182,6 @@ async function walkTypeScriptFiles(dir) {
52724
53182
  try {
52725
53183
  entries = (await readdir12(dir)).sort();
52726
53184
  } catch {
52727
- return out;
52728
53185
  }
52729
53186
  for (const entry of entries) {
52730
53187
  if (entry === "node_modules" || entry === "dist") continue;
@@ -55739,7 +56196,13 @@ async function tryReadBatchPath(path, cwd, reader) {
55739
56196
  import { type as type104 } from "arktype";
55740
56197
  var DBX_AUTH_CLAIM_LOOKUP_TOOL = {
55741
56198
  name: "dbx_auth_claim_lookup",
55742
- 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"),
55743
56206
  inputSchema: {
55744
56207
  type: "object",
55745
56208
  properties: {
@@ -55962,7 +56425,14 @@ function sourceLink2(path, line) {
55962
56425
  import { type as type106 } from "arktype";
55963
56426
  var DBX_AUTH_ROLE_LOOKUP_TOOL = {
55964
56427
  name: "dbx_auth_role_lookup",
55965
- 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"),
55966
56436
  inputSchema: {
55967
56437
  type: "object",
55968
56438
  properties: {
@@ -56145,7 +56615,11 @@ function sourceLink3(path, line) {
56145
56615
  import { type as type107 } from "arktype";
56146
56616
  var DBX_AUTH_TOKEN_EXPLAIN_TOOL = {
56147
56617
  name: "dbx_auth_token_explain",
56148
- 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"),
56149
56623
  inputSchema: {
56150
56624
  type: "object",
56151
56625
  properties: {
@@ -56604,7 +57078,13 @@ function createAuthClusterTools(registry) {
56604
57078
  function registerTools(server, options = {}) {
56605
57079
  const underlyingServer = server.server;
56606
57080
  const tools = [...DBX_TOOLS];
56607
- 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
+ );
56608
57088
  if (options.forgeFieldRegistry !== void 0) {
56609
57089
  tools.push(createLookupFormTool({ registry: options.forgeFieldRegistry }), createSearchFormTool({ registry: options.forgeFieldRegistry }), createFormScaffoldTool({ registry: options.forgeFieldRegistry }));
56610
57090
  }
@@ -56858,7 +57338,25 @@ async function loadServerContext(options = {}) {
56858
57338
  await emitDownstreamHints({ cwd, externalCounts, onDownstreamHints: options.onDownstreamHints });
56859
57339
  const context = {
56860
57340
  resources: { semanticTypeRegistry: registry, forgeFieldRegistry: forgeRegistry, pipeRegistry, utilRegistry, modelSnapshotFieldRegistry, modelFirebaseIndexRegistry, uiComponentRegistry: uiRegistry, actionRegistry, filterRegistry, tokenRegistry, cssUtilityRegistry, authRegistry, cwd },
56861
- 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
+ }
56862
57360
  };
56863
57361
  return context;
56864
57362
  }
@@ -56962,7 +57460,14 @@ async function resolveAuthRegistry(input) {
56962
57460
  }
56963
57461
  function reportAuthLoaderResult(result) {
56964
57462
  const { registry, scannedFiles, fileWarnings, extractWarnings, extractedAppCount, extractedClaimCount } = result;
56965
- 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");
56966
57471
  process.stderr.write(`${summary}
56967
57472
  `);
56968
57473
  for (const warning of fileWarnings) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-components-mcp",
3
- "version": "14.0.0",
3
+ "version": "14.0.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "dbx-components-mcp": "dbx-components-mcp.js"
@@ -11,7 +11,7 @@
11
11
  "ts-morph": "^21.0.0"
12
12
  },
13
13
  "peerDependencies": {
14
- "@dereekb/dbx-cli": "14.0.0",
15
- "@dereekb/util": "14.0.0"
14
+ "@dereekb/dbx-cli": "14.0.1",
15
+ "@dereekb/util": "14.0.1"
16
16
  }
17
17
  }