@duckcodeailabs/dql-agent 1.13.1 → 1.13.2

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.
@@ -58,7 +58,7 @@ export interface AppPlanGenUi {
58
58
  export interface AppPlanFilter {
59
59
  id: string;
60
60
  label: string;
61
- type: "date" | "daterange" | "select" | "string" | "number";
61
+ type: "date" | "daterange" | "select" | "search" | "string" | "number";
62
62
  default?: unknown;
63
63
  options?: string[];
64
64
  bindsTo?: string;
@@ -223,4 +223,22 @@ export declare const APP_BUILDER_SKILLS: AppBuilderSkill[];
223
223
  export declare function planAppFromPrompt(input: PlanAppFromPromptInput): AppPlan;
224
224
  export declare function validateAppPlan(plan: AppPlan, kg: KGStore): AppPlanValidationResult;
225
225
  export declare function generateAppFromPlan(projectRoot: string, plan: AppPlan, kg: KGStore, options?: GenerateAppFromPlanOptions): GeneratedAppPackage;
226
+ /**
227
+ * Compile a validated AppPlan into typed App/dashboard documents without
228
+ * touching the user's project. App Studio uses this for AI proposal diffs;
229
+ * only the later Publish to Project service writes canonical App source.
230
+ */
231
+ export declare function materializeAppPlanDocuments(plan: AppPlan, kg: KGStore, options?: GenerateAppFromPlanOptions): Pick<GeneratedAppPackage, "app" | "dashboards">;
232
+ /**
233
+ * A tile title a stakeholder can read.
234
+ *
235
+ * A certified block's KG node name is its id — `monthly_revenue` — so using it
236
+ * directly labelled every AI-built tile with an identifier, which also made the
237
+ * titles look like system fields rather than something the author may rename.
238
+ * Prefer the lead clause of the block's own description, which authors already
239
+ * write as a summary ("Monthly gross order revenue and order count."), and fall
240
+ * back to the de-slugged id when that clause is missing or too long to sit in a
241
+ * tile header.
242
+ */
243
+ export declare function humanTileTitle(name: string, description?: string): string;
226
244
  //# sourceMappingURL=app-builder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-builder.d.ts","sourceRoot":"","sources":["../src/app-builder.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,WAAW,EAEhB,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,0BAA0B,EAE/B,KAAK,0BAA0B,EAC/B,KAAK,6BAA6B,EAClC,KAAK,2BAA2B,EAEhC,KAAK,kBAAkB,EACxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAIlD,MAAM,MAAM,OAAO,GAAG,UAAU,GAAG,aAAa,CAAC;AAEjD,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,GAAG,OAAO,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC/C,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE;QAAE,SAAS,EAAE,KAAK,GAAG,QAAQ,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1D;AAED,MAAM,WAAW,mBAAmB;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,SAAS,GAAG,KAAK,CAAC;IAC1B,MAAM,EAAE,iBAAiB,GAAG,gBAAgB,GAAG,KAAK,CAAC;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,iBAAiB,CAAC;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,MAAM,iBAAiB,GACzB,2BAA2B,GAC3B,yBAAyB,GACzB,sBAAsB,GACtB,yBAAyB,GACzB,wBAAwB,GACxB,cAAc,CAAC;AAEnB,MAAM,MAAM,eAAe,GACvB,iBAAiB,GACjB,gBAAgB,GAChB,mBAAmB,GACnB,WAAW,CAAC;AAEhB,MAAM,MAAM,qBAAqB,GAC7B,mBAAmB,GACnB,mBAAmB,GACnB,iBAAiB,GACjB,kBAAkB,GAClB,cAAc,GACd,cAAc,GACd,oBAAoB,CAAC;AAEzB,MAAM,MAAM,eAAe,GACvB,kBAAkB,GAClB,KAAK,GACL,OAAO,GACP,WAAW,GACX,UAAU,GACV,OAAO,GACP,UAAU,GACV,WAAW,CAAC;AAEhB,MAAM,MAAM,iBAAiB,GACzB,WAAW,GACX,iBAAiB,GACjB,aAAa,CAAC;AAElB,MAAM,MAAM,qBAAqB,GAC7B,eAAe,GACf,eAAe,GACf,cAAc,GACd,oBAAoB,CAAC;AAEzB,MAAM,MAAM,mBAAmB,GAC3B,MAAM,GACN,SAAS,GACT,UAAU,GACV,MAAM,GACN,MAAM,GACN,MAAM,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAC7B,eAAe,GACf,WAAW,GACX,YAAY,GACZ,cAAc,GACd,eAAe,GACf,YAAY,GACZ,cAAc,GACd,iBAAiB,GACjB,gBAAgB,CAAC;AAErB,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,CAAC,CAAC;IACX,SAAS,EAAE,qBAAqB,CAAC;IACjC,IAAI,EAAE,eAAe,CAAC;IACtB,YAAY,EAAE,mBAAmB,CAAC;IAClC,oBAAoB,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACjD,qBAAqB,EAAE,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;IACpD,UAAU,CAAC,EAAE;QACX,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,CAAC,CAAC,EAAE,MAAM,CAAC;QACX,CAAC,CAAC,EAAE,MAAM,CAAC;QACX,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,iBAAiB,CAAC;IAC9B,YAAY,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,qBAAqB,EAAE,CAAC;IACzC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,eAAe,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,yBAAyB,CAAC;IACrC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAChC,aAAa,EAAE,WAAW,GAAG,aAAa,CAAC;IAC3C,YAAY,EAAE,WAAW,GAAG,aAAa,GAAG,iBAAiB,CAAC;IAC9D,cAAc,CAAC,EAAE,0BAA0B,EAAE,CAAC;IAC9C,iBAAiB,CAAC,EAAE,6BAA6B,EAAE,CAAC;IACpD,cAAc,CAAC,EAAE,2BAA2B,EAAE,CAAC;IAC/C,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE;QACR,IAAI,EAAE,eAAe,CAAC;QACtB,sBAAsB,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;QACnD,cAAc,EAAE,MAAM,CAAC;QACvB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,UAAU,EAAE,iBAAiB,CAAC;QAC9B,eAAe,EAAE,qBAAqB,EAAE,CAAC;QACzC,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,YAAY,CAAC;KACrB,CAAC;CACH;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,WAAW,GAAG,aAAa,GAAG,iBAAiB,CAAC;CAC/D;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,qBAAqB,GAAG,cAAc,CAAC;IAC/C,YAAY,EAAE,aAAa,GAAG,iBAAiB,CAAC;IAChD,MAAM,EAAE,aAAa,CAAC;IACtB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,gBAAgB,EAAE,qBAAqB,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,iBAAiB,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,CAAC,CAAC;IACX,IAAI,EAAE,OAAO,CAAC;IACd,8FAA8F;IAC9F,iBAAiB,EAAE,UAAU,GAAG,gBAAgB,CAAC;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,mBAAmB,EAAE,mBAAmB,EAAE,CAAC;IAC3C,iBAAiB,EAAE,0BAA0B,CAAC;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE;QACR,WAAW,EAAE,eAAe,GAAG,aAAa,CAAC;QAC7C,cAAc,EAAE,MAAM,CAAC;QACvB,cAAc,EAAE,qBAAqB,CAAC;QACtC,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,gBAAgB,EAAE,KAAK,CAAC;YACtB,MAAM,EAAE,MAAM,CAAC;YACf,IAAI,EAAE,MAAM,CAAC;YACb,IAAI,EAAE,MAAM,CAAC;YACb,MAAM,EAAE,MAAM,CAAC;SAChB,CAAC,CAAC;QACH,eAAe,EAAE,MAAM,EAAE,CAAC;QAC1B,aAAa,EAAE,mBAAmB,EAAE,CAAC;QACrC,eAAe,EAAE,MAAM,CAAC;QACxB,eAAe,EAAE,MAAM,CAAC;QACxB,WAAW,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;IACF,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC9B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,aAAa,EAAE,aAAa,EAAE,CAAC;IAC/B,gBAAgB,EAAE,KAAK,CAAC;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,iBAAiB,CAAC;KAC/B,CAAC,CAAC;IACH,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,mBAAmB,EAAE,CAAC;IACrC,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,EAAE;QACR,cAAc,EAAE,MAAM,CAAC;QACvB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,eAAe,GAAG,aAAa,CAAC;IAC9C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,sBAAsB,EAAE,CAAC;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;sEAEkE;IAClE,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,WAAW,CAAC;IACjB,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAChC,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,eAAO,MAAM,kBAAkB,EAAE,eAAe,EAqC/C,CAAC;AAmLF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAwOxE;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,OAAO,EACb,EAAE,EAAE,OAAO,GACV,uBAAuB,CA4GzB;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,OAAO,EACb,EAAE,EAAE,OAAO,EACX,OAAO,GAAE,0BAA+B,GACvC,mBAAmB,CA8LrB"}
1
+ {"version":3,"file":"app-builder.d.ts","sourceRoot":"","sources":["../src/app-builder.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { createHash } from "node:crypto";
3
3
  import { join, relative } from "node:path";
4
+ import { tmpdir } from "node:os";
4
5
  import { parseAppDocument, parseDashboardDocument, suggestAppId, } from "@duckcodeailabs/dql-core";
5
6
  import { buildAnalysisQuestionPlan } from "./metadata/analysis-planner.js";
6
7
  import { certifiedFitAllowsTier1, evaluateCertifiedBlockFit } from "./metadata/block-fit.js";
@@ -36,13 +37,25 @@ export const APP_BUILDER_SKILLS = [
36
37
  description: "Keep generated analysis review-required and attach concrete next steps before stakeholder use.",
37
38
  },
38
39
  ];
39
- function appRequirementsForPrompt(prompt) {
40
+ function appRequirementsForPrompt(prompt, certifiedNodes = []) {
40
41
  const analysis = buildAnalysisQuestionPlan(prompt);
41
42
  const measures = analysis.requestedShape.measures.length
42
43
  ? analysis.requestedShape.measures
43
44
  : analysis.metricTerms;
44
- const dimensions = analysis.requestedShape.dimensions;
45
- const filters = analysis.requestedShape.filters;
45
+ const explicitMembers = explicitMemberFilters(prompt);
46
+ const requestedDimensions = Array.from(new Set([
47
+ ...analysis.requestedShape.dimensions,
48
+ ...explicitMembers.map((member) => member.dimension),
49
+ ]));
50
+ const availableDimensions = Array.from(new Set(certifiedNodes.flatMap((node) => [
51
+ ...(node.dimensions ?? []),
52
+ ...(node.allowedFilters ?? []),
53
+ ]))).filter((dimension) => !/^(top_?n|limit)$/i.test(dimension));
54
+ const dimensions = requestedDimensions.length > 0 ? requestedDimensions : availableDimensions.slice(0, 3);
55
+ const filters = Array.from(new Set([
56
+ ...analysis.requestedShape.filters,
57
+ ...explicitMembers.map((member) => member.value),
58
+ ]));
46
59
  const ranking = analysis.requestedShape.topN
47
60
  ? { direction: analysis.requestedShape.rankingDirection ?? "top", limit: analysis.requestedShape.topN.n }
48
61
  : undefined;
@@ -56,18 +69,31 @@ function appRequirementsForPrompt(prompt) {
56
69
  grain: analysis.requestedShape.grain,
57
70
  ...(ranking ? { ranking } : {}),
58
71
  }];
59
- const wantsTrend = analysis.mode === "trend" || /\btrend|over time|weekly|monthly|quarterly\b/i.test(prompt);
72
+ if (measures.length > 0 && requirements[0]?.role !== "kpi") {
73
+ requirements.unshift({
74
+ id: "headline",
75
+ question: `Headline ${measures.join(" and ")}`,
76
+ role: "kpi",
77
+ measures,
78
+ dimensions: [],
79
+ filters,
80
+ });
81
+ }
82
+ const sourceTimeDimension = availableDimensions.find((dimension) => /(_at$|_date$|_time$|^date$|^month$|^week$|^day$|quarter|year)/i.test(dimension));
83
+ const wantsTrend = Boolean(sourceTimeDimension) || analysis.mode === "trend" || /\btrend|over time|weekly|monthly|quarterly\b/i.test(prompt);
60
84
  if (wantsTrend && !requirements.some((requirement) => requirement.role === "trend")) {
61
85
  requirements.push({
62
86
  id: "trend",
63
87
  question: `${measures.join(" and ") || "primary metric"} over time`,
64
88
  role: "trend",
65
89
  measures,
66
- dimensions: analysis.timeTerms.length ? analysis.timeTerms : ["time"],
90
+ dimensions: analysis.timeTerms.length ? analysis.timeTerms : [sourceTimeDimension ?? "time"],
67
91
  filters,
68
92
  });
69
93
  }
70
94
  for (const dimension of dimensions.slice(0, 2)) {
95
+ if (/(_at$|_date$|_time$|^date$|^month$|^week$|^day$|quarter|year)/i.test(dimension))
96
+ continue;
71
97
  if (requirements[0]?.dimensions.includes(dimension) && dimensions.length === 1)
72
98
  continue;
73
99
  requirements.push({
@@ -79,8 +105,25 @@ function appRequirementsForPrompt(prompt) {
79
105
  filters,
80
106
  });
81
107
  }
108
+ const detailDimensions = dimensions.filter((dimension) => !/(_at$|_date$|_time$|^date$|^month$|^week$|^day$|quarter|year)/i.test(dimension));
109
+ if ((ranking || detailDimensions.length > 0) && !requirements.some((requirement) => requirement.role === "detail")) {
110
+ requirements.push({
111
+ id: "detail",
112
+ question: `Detailed evidence for ${measures.join(" and ") || prompt}`,
113
+ role: "detail",
114
+ measures,
115
+ dimensions: detailDimensions.slice(0, 3),
116
+ filters,
117
+ grain: analysis.requestedShape.grain,
118
+ ...(ranking ? { ranking } : {}),
119
+ });
120
+ }
82
121
  return requirements;
83
122
  }
123
+ function explicitMemberFilters(prompt) {
124
+ const matches = prompt.matchAll(/\bfor\s+([a-z0-9][a-z0-9_-]*)\s+((?:product\s+)?category|segment|status|region|channel|country|type)\b/gi);
125
+ return Array.from(matches, (match) => ({ value: match[1], dimension: match[2] }));
126
+ }
84
127
  function certifiedCoverageForRequirement(requirement, nodes) {
85
128
  const plan = buildAnalysisQuestionPlan(requirement.question);
86
129
  const ranked = nodes.map((node) => ({
@@ -151,6 +194,12 @@ function semanticTileForRequirement(kg, requirement, domain, snapshotId) {
151
194
  if (!metric)
152
195
  return null;
153
196
  const semanticModelRefs = Array.from(new Set(modelNodes.map((node) => node.name))).slice(0, 4);
197
+ const semanticFilters = semanticFiltersForRequirement(requirement);
198
+ // A semantic fallback may close a filtered requirement only when every
199
+ // requested member is bound to a named dimension. Recording the filter in a
200
+ // fingerprint without applying it would make coverage untruthful.
201
+ if (requirement.filters.length > 0 && semanticFilters.length !== requirement.filters.length)
202
+ return null;
154
203
  const id = `semantic-${requirement.id}`;
155
204
  const fingerprintPayload = JSON.stringify({
156
205
  metric: metric.nodeId,
@@ -164,6 +213,7 @@ function semanticTileForRequirement(kg, requirement, domain, snapshotId) {
164
213
  provider: metric.provenance?.toLowerCase().includes("metricflow") ? "metricflow" : "native",
165
214
  metrics: [metric.name],
166
215
  ...(requirement.dimensions.length ? { dimensions: requirement.dimensions } : {}),
216
+ ...(semanticFilters.length ? { filters: semanticFilters } : {}),
167
217
  ...(requirement.ranking ? {
168
218
  orderBy: [{ field: metric.name, direction: requirement.ranking.direction === "bottom" ? "asc" : "desc" }],
169
219
  limit: requirement.ranking.limit,
@@ -189,6 +239,15 @@ function semanticTileForRequirement(kg, requirement, domain, snapshotId) {
189
239
  reviewStatus: "review_required",
190
240
  trustState: "review_required",
191
241
  rationale: "Governed semantic coverage for a requirement not fully covered by a compatible certified block.",
242
+ ...(semanticFilters.length ? {
243
+ filterBindings: semanticFilters.map((filter) => ({
244
+ filter: slugify(filter.field),
245
+ binding: filter.field,
246
+ mode: "semantic",
247
+ capability: "supported",
248
+ required: true,
249
+ })),
250
+ } : {}),
192
251
  sourceEvidence: [
193
252
  { source: metric.nodeId, nodeId: metric.nodeId, kind: metric.kind, reason: "Governed semantic metric; result review is still required", trustState: "review_required" },
194
253
  ...modelNodes.slice(0, 4).map((node) => ({ source: node.nodeId, nodeId: node.nodeId, kind: node.kind, reason: "Semantic model used by the canonical query", trustState: "review_required" })),
@@ -196,6 +255,39 @@ function semanticTileForRequirement(kg, requirement, domain, snapshotId) {
196
255
  reviewTasks: ["Review semantic definition freshness and result shape before stakeholder publication."],
197
256
  };
198
257
  }
258
+ function semanticFiltersForRequirement(requirement) {
259
+ const question = requirement.question.toLowerCase();
260
+ return requirement.filters.flatMap((value) => {
261
+ const valueIndex = question.indexOf(value.toLowerCase());
262
+ if (valueIndex < 0)
263
+ return [];
264
+ const candidates = requirement.dimensions
265
+ .map((dimension) => ({
266
+ dimension,
267
+ index: question.indexOf(dimension.toLowerCase(), valueIndex + value.length),
268
+ }))
269
+ .filter((candidate) => candidate.index >= 0 && candidate.index - valueIndex <= 64)
270
+ .sort((left, right) => left.index - right.index);
271
+ const dimension = inferFilterFieldFromQuestion(requirement.question, value) ?? candidates[0]?.dimension;
272
+ if (!dimension)
273
+ return [];
274
+ return [{
275
+ field: slugify(dimension).replace(/-/g, "_"),
276
+ operator: "=",
277
+ value,
278
+ }];
279
+ });
280
+ }
281
+ function inferFilterFieldFromQuestion(question, value) {
282
+ const escaped = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
283
+ const dimensionWord = /(category|type|segment|status|region|country|channel|customer|product|team|season|date|period)$/i;
284
+ const after = new RegExp(`${escaped}\\s+([a-z0-9_ -]{2,40})$`, "i").exec(question.trim())?.[1]?.trim();
285
+ if (after && dimensionWord.test(after))
286
+ return after;
287
+ const before = new RegExp(`([a-z0-9_ -]{2,40})\\s+${escaped}(?:\\s|$)`, "i").exec(question)?.[1]?.trim();
288
+ const beforeTail = before?.split(/\s+/).slice(-2).join(" ");
289
+ return beforeTail && dimensionWord.test(beforeTail) ? beforeTail : undefined;
290
+ }
199
291
  export function planAppFromPrompt(input) {
200
292
  const prompt = input.prompt.trim();
201
293
  if (!prompt)
@@ -208,17 +300,22 @@ export function planAppFromPrompt(input) {
208
300
  const maxCertifiedTiles = input.maxCertifiedTiles ?? 4;
209
301
  const targetCertifiedTiles = Math.max(maxCertifiedTiles, input.preferredBlockIds?.length ?? 0);
210
302
  const preferredNodes = findPreferredCertifiedBlockNodes(input.kg, input.preferredBlockIds ?? []);
211
- const matchedNodes = findCertifiedBlockNodes(input.kg, prompt, domain, Math.max(targetCertifiedTiles * 3, preferredNodes.length));
212
- const certifiedNodes = dedupeCertifiedBlockNodes(mergeCertifiedBlockNodes(preferredNodes, matchedNodes), new Set(preferredNodes.map((node) => node.nodeId))).slice(0, targetCertifiedTiles);
303
+ const hasExplicitSelection = (input.preferredBlockIds?.length ?? 0) > 0;
304
+ const matchedNodes = hasExplicitSelection ? [] : findCertifiedBlockNodes(input.kg, prompt, domain, Math.max(targetCertifiedTiles * 3, preferredNodes.length));
305
+ // App Studio source review is an exact allow-list. Once the author chooses
306
+ // blocks, do not silently reintroduce an AI-ranked source they removed.
307
+ const certifiedNodes = dedupeCertifiedBlockNodes(hasExplicitSelection ? preferredNodes : mergeCertifiedBlockNodes(preferredNodes, matchedNodes), new Set(preferredNodes.map((node) => node.nodeId))).slice(0, targetCertifiedTiles);
213
308
  const contextNodes = findCertifiedContextNodes(input.kg, prompt, domain, 6);
214
309
  // Bind the global filter bar to what the certified tiles actually accept (their
215
310
  // declared allowedFilters), not prompt words — falling back to prompt inference
216
311
  // only when no tile declares any filter. Keeps the dashboard's filters dynamic.
217
312
  const promptFilters = inferFilters(prompt);
218
313
  const blockFilters = filtersFromCertifiedNodes(certifiedNodes);
219
- const filters = blockFilters.length > 0 ? mergeAppFilters(blockFilters, promptFilters) : promptFilters;
314
+ let filters = blockFilters.length > 0 ? mergeAppFilters(blockFilters, promptFilters) : promptFilters;
220
315
  const candidateCertifiedTiles = certifiedNodes.map((node, index) => tileFromCertifiedNode(node, index, analysisIntent, filters));
221
- const requirements = appRequirementsForPrompt(prompt);
316
+ const requirements = appRequirementsForPrompt(prompt, certifiedNodes);
317
+ const allowSemanticFallback = input.allowSemanticQueries !== false
318
+ && !promptExplicitlyUsesNamedCertifiedBlocks(prompt, certifiedNodes);
222
319
  const coverage = [];
223
320
  const selectedCertifiedIds = new Set();
224
321
  const semanticTiles = [];
@@ -238,7 +335,7 @@ export function planAppFromPrompt(input) {
238
335
  });
239
336
  continue;
240
337
  }
241
- const semanticTile = input.allowSemanticQueries === false
338
+ const semanticTile = !allowSemanticFallback
242
339
  ? null
243
340
  : semanticTileForRequirement(input.kg, requirement, domain, input.snapshotId);
244
341
  if (semanticTile) {
@@ -263,6 +360,15 @@ export function planAppFromPrompt(input) {
263
360
  });
264
361
  }
265
362
  }
363
+ const semanticFilters = semanticTiles.flatMap((tile) => (tile.semantic?.filters ?? []).map((filter) => ({
364
+ id: slugify(filter.field),
365
+ label: titleCase(filter.field.replace(/[_.]+/g, " ")),
366
+ type: "select",
367
+ default: filter.value,
368
+ options: [String(filter.value)],
369
+ bindsTo: filter.field,
370
+ })));
371
+ filters = uniquePlanFilters([...filters, ...semanticFilters]);
266
372
  // Keep compatible certified context only. For legacy unshaped prompts, retain
267
373
  // the best certified candidates so existing simple App generation stays useful.
268
374
  // Requirement coverage is strict, while explicitly discovered certified
@@ -398,6 +504,21 @@ export function planAppFromPrompt(input) {
398
504
  },
399
505
  };
400
506
  }
507
+ /**
508
+ * A user who explicitly names one or more certified blocks has selected the
509
+ * analytical contracts they want to use. Keep uncovered requirements visible
510
+ * instead of silently broadening that request with semantic fallbacks. Generic
511
+ * "governed sources" requests still retain certified-first semantic fallback.
512
+ */
513
+ function promptExplicitlyUsesNamedCertifiedBlocks(prompt, nodes) {
514
+ if (!/\bcertified\b/i.test(prompt) || !/\bblocks?\b/i.test(prompt))
515
+ return false;
516
+ const normalizedPrompt = canonicalRequirementText(prompt);
517
+ return nodes.some((node) => {
518
+ const normalizedName = canonicalRequirementText(node.name).trim();
519
+ return normalizedName.split(/\s+/).length >= 2 && normalizedPrompt.includes(` ${normalizedName} `);
520
+ });
521
+ }
401
522
  export function validateAppPlan(plan, kg) {
402
523
  const issues = [];
403
524
  let certifiedTiles = 0;
@@ -575,43 +696,52 @@ export function generateAppFromPlan(projectRoot, plan, kg, options = {}) {
575
696
  ? { copilot: { suggestedQuestions: options.copilotQuestions.slice(0, 8) } }
576
697
  : {}),
577
698
  };
578
- const dashboards = plan.pages.map((page, pageIndex) => ({
579
- version: 1,
580
- id: page.id,
581
- ...(options.narration && pageIndex === 0
582
- ? { sections: buildStorySections(page.tiles, options.narration) }
583
- : {}),
584
- ...(pageIndex === 0 ? { story: plan.storyEvidencePlan } : {}),
585
- metadata: {
586
- title: page.title,
587
- description: page.description ?? plan.planning.displayStrategy,
588
- domain: plan.domain,
589
- audience: plan.audience,
590
- visibility: "private",
591
- lifecycle: "draft",
592
- tags: plan.tags,
593
- businessOutcome: plan.planning.normalizedGoal,
594
- businessOwner: plan.owner,
595
- decisionUse: plan.planning.displayStrategy,
596
- reviewCadence: inferReviewCadence(plan.prompt),
597
- caveats: plan.caveats,
598
- },
599
- filters: page.filters.map((filter) => ({
600
- id: filter.id,
601
- type: filter.type,
602
- default: filter.default,
603
- ...(filter.options?.length ? { options: filter.options } : {}),
604
- bindsTo: filter.bindsTo,
605
- })),
606
- layout: {
607
- kind: "grid",
608
- cols: 12,
609
- rowHeight: 80,
610
- items: options.narration && pageIndex === 0
611
- ? buildStoryLayoutItems(page.tiles, options.narration)
612
- : buildLayoutItems(page.tiles),
613
- },
614
- }));
699
+ const dashboards = plan.pages.map((page, pageIndex) => {
700
+ const items = options.narration && pageIndex === 0
701
+ ? buildStoryLayoutItems(page.tiles, options.narration)
702
+ : buildLayoutItems(page.tiles);
703
+ return {
704
+ version: 2,
705
+ id: page.id,
706
+ ...(options.narration && pageIndex === 0
707
+ ? { sections: buildStorySections(page.tiles, options.narration) }
708
+ : {}),
709
+ ...(pageIndex === 0 ? { story: plan.storyEvidencePlan } : {}),
710
+ metadata: {
711
+ title: page.title,
712
+ description: page.description ?? plan.planning.displayStrategy,
713
+ domain: plan.domain,
714
+ audience: plan.audience,
715
+ visibility: "private",
716
+ lifecycle: "draft",
717
+ tags: plan.tags,
718
+ businessOutcome: plan.planning.normalizedGoal,
719
+ businessOwner: plan.owner,
720
+ decisionUse: plan.planning.displayStrategy,
721
+ reviewCadence: inferReviewCadence(plan.prompt),
722
+ caveats: plan.caveats,
723
+ },
724
+ filters: page.filters.map((filter) => ({
725
+ id: filter.id,
726
+ label: filter.label,
727
+ type: filter.type,
728
+ default: filter.default,
729
+ ...(filter.options?.length ? { options: filter.options } : {}),
730
+ bindsTo: filter.bindsTo,
731
+ ...(filter.bindsTo ? { field: { name: filter.bindsTo } } : {}),
732
+ ...(filter.options?.length
733
+ ? { optionSource: { mode: "static" } }
734
+ : {}),
735
+ })),
736
+ layout: {
737
+ kind: "grid",
738
+ cols: 12,
739
+ rowHeight: 80,
740
+ items,
741
+ responsive: responsiveLayoutsForItems(items),
742
+ },
743
+ };
744
+ });
615
745
  const appValidation = parseAppDocument(JSON.stringify(app), join(appDir, "dql.app.json"));
616
746
  if (appValidation.errors.length > 0 || !appValidation.document) {
617
747
  throw new Error(`Generated app is invalid: ${appValidation.errors.map((e) => e.message).join("; ")}`);
@@ -640,10 +770,30 @@ export function generateAppFromPlan(projectRoot, plan, kg, options = {}) {
640
770
  ].map((path) => relative(projectRoot, path)),
641
771
  };
642
772
  }
773
+ /**
774
+ * Compile a validated AppPlan into typed App/dashboard documents without
775
+ * touching the user's project. App Studio uses this for AI proposal diffs;
776
+ * only the later Publish to Project service writes canonical App source.
777
+ */
778
+ export function materializeAppPlanDocuments(plan, kg, options = {}) {
779
+ const scratchRoot = mkdtempSync(join(tmpdir(), "dql-app-plan-"));
780
+ try {
781
+ const generated = generateAppFromPlan(scratchRoot, plan, kg, { ...options, overwrite: true });
782
+ return { app: generated.app, dashboards: generated.dashboards };
783
+ }
784
+ finally {
785
+ rmSync(scratchRoot, { recursive: true, force: true });
786
+ }
787
+ }
643
788
  function findCertifiedBlockNodes(kg, prompt, domain, limit) {
644
789
  const hits = kg.search({
645
790
  query: prompt,
646
- domain,
791
+ // `general` is the Studio's initial, unscoped page domain. Treating it as
792
+ // a literal KG domain hid every real project block from AI-created drafts
793
+ // until the author manually chose a domain. An unscoped draft must search
794
+ // the whole governed catalog; a concrete domain remains a hard preference
795
+ // with the existing cross-domain fallback below.
796
+ domain: domain === "general" ? undefined : domain,
647
797
  kinds: ["block"],
648
798
  limit: limit * 3,
649
799
  });
@@ -716,6 +866,14 @@ function dedupeCertifiedBlockNodes(nodes, preferredNodeIds = new Set()) {
716
866
  continue;
717
867
  }
718
868
  const current = selected[duplicateIndex];
869
+ // An explicit App Studio selection is author intent, not a search result.
870
+ // Keep both chosen blocks even when their business/topic fingerprints are
871
+ // similar; automatic discovery may still dedupe everything else.
872
+ if (preferredNodeIds.has(current.nodeId) && preferredNodeIds.has(node.nodeId)) {
873
+ signatureIndex.set(node.nodeId, selected.length);
874
+ selected.push(node);
875
+ continue;
876
+ }
719
877
  if (certifiedBlockSelectionScore(node, preferredNodeIds) > certifiedBlockSelectionScore(current, preferredNodeIds)) {
720
878
  selected[duplicateIndex] = node;
721
879
  signatureIndex.set(node.nodeId, duplicateIndex);
@@ -983,7 +1141,7 @@ function displayForCertifiedNode(node, viz, index, intent) {
983
1141
  followUpActions,
984
1142
  rationale,
985
1143
  genUi: buildGenUiContract({
986
- title: node.name,
1144
+ title: humanTileTitle(node.name, node.description),
987
1145
  role,
988
1146
  viz,
989
1147
  text,
@@ -1166,7 +1324,7 @@ function tileFromCertifiedNode(node, index, intent, globalFilters = []) {
1166
1324
  const parameterBindings = parameterBindingsForCertifiedNode(node, filterBindings);
1167
1325
  return {
1168
1326
  id: slugify(blockId) || `certified-${index + 1}`,
1169
- title: node.name,
1327
+ title: humanTileTitle(blockId, node.description),
1170
1328
  kind: "certified_block",
1171
1329
  description: node.description,
1172
1330
  blockId,
@@ -1453,6 +1611,32 @@ function buildLayoutItems(tiles) {
1453
1611
  return item;
1454
1612
  });
1455
1613
  }
1614
+ function responsiveLayoutsForItems(items) {
1615
+ return {
1616
+ wide: { kind: "grid", cols: 12, rowHeight: 80, items },
1617
+ medium: { kind: "grid", cols: 6, rowHeight: 80, items: projectItemsToColumns(items, 6) },
1618
+ narrow: { kind: "grid", cols: 1, rowHeight: 80, items: projectItemsToColumns(items, 1) },
1619
+ };
1620
+ }
1621
+ function projectItemsToColumns(items, cols) {
1622
+ let x = 0;
1623
+ let y = 0;
1624
+ let rowHeight = 0;
1625
+ return [...items]
1626
+ .sort((left, right) => left.y - right.y || left.x - right.x)
1627
+ .map((item) => {
1628
+ const width = cols === 1 ? 1 : Math.min(cols, Math.max(2, Math.ceil(item.w / 2)));
1629
+ if (x + width > cols) {
1630
+ x = 0;
1631
+ y += rowHeight || item.h;
1632
+ rowHeight = 0;
1633
+ }
1634
+ const projected = { ...item, x, y, w: width };
1635
+ x += width;
1636
+ rowHeight = Math.max(rowHeight, item.h);
1637
+ return projected;
1638
+ });
1639
+ }
1456
1640
  function markdownForGeneratedPlanTile(tile) {
1457
1641
  const reviewTasks = tile.reviewTasks?.length
1458
1642
  ? tile.reviewTasks.map((task) => `- ${task}`).join("\n")
@@ -1795,12 +1979,15 @@ function uniquePlanFilters(filters) {
1795
1979
  }
1796
1980
  const APP_FILTER_TIME_RE = /(_at$|_date$|_time$|_ts$|^date$|^month$|^week$|^day$|ordered_at|created)/i;
1797
1981
  const APP_FILTER_NUMBER_RE = /(top[_-]?n|limit|count|amount|year|score|rank)/i;
1982
+ const APP_FILTER_SEARCH_RE = /(_name$|_id$|^name$|search|email)/i;
1798
1983
  /** Map a block filter column to the dashboard control the runtime filter engine renders. */
1799
1984
  function controlTypeForColumn(column) {
1800
1985
  if (APP_FILTER_TIME_RE.test(column))
1801
1986
  return "daterange";
1802
1987
  if (APP_FILTER_NUMBER_RE.test(column))
1803
1988
  return "number";
1989
+ if (APP_FILTER_SEARCH_RE.test(column))
1990
+ return "search";
1804
1991
  return "select"; // categorical → dropdown (options filled by /api/dashboard/filter-options)
1805
1992
  }
1806
1993
  /**
@@ -1961,6 +2148,29 @@ function cleanFieldHints(input) {
1961
2148
  }
1962
2149
  return Object.keys(out).length > 0 ? out : undefined;
1963
2150
  }
2151
+ /** Longest tile title we will lift out of a block description. */
2152
+ const MAX_DERIVED_TILE_TITLE = 48;
2153
+ /**
2154
+ * A tile title a stakeholder can read.
2155
+ *
2156
+ * A certified block's KG node name is its id — `monthly_revenue` — so using it
2157
+ * directly labelled every AI-built tile with an identifier, which also made the
2158
+ * titles look like system fields rather than something the author may rename.
2159
+ * Prefer the lead clause of the block's own description, which authors already
2160
+ * write as a summary ("Monthly gross order revenue and order count."), and fall
2161
+ * back to the de-slugged id when that clause is missing or too long to sit in a
2162
+ * tile header.
2163
+ */
2164
+ export function humanTileTitle(name, description) {
2165
+ const lead = (description ?? "").split(/[.;]/)[0]?.trim() ?? "";
2166
+ if (lead.length >= 3 && lead.length <= MAX_DERIVED_TILE_TITLE && /\s/.test(lead)) {
2167
+ return lead.charAt(0).toUpperCase() + lead.slice(1);
2168
+ }
2169
+ const words = name.replace(/[_-]+/g, " ").trim();
2170
+ if (!words)
2171
+ return name;
2172
+ return words.charAt(0).toUpperCase() + words.slice(1);
2173
+ }
1964
2174
  function titleCase(value) {
1965
2175
  return value
1966
2176
  .split(/\s+/)