@duckcodeailabs/dql-agent 1.7.0 → 1.7.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/dist/app-builder.d.ts +36 -3
- package/dist/app-builder.d.ts.map +1 -1
- package/dist/app-builder.js +269 -14
- package/dist/app-builder.js.map +1 -1
- package/dist/dashboard-story.d.ts +36 -0
- package/dist/dashboard-story.d.ts.map +1 -0
- package/dist/dashboard-story.js +140 -0
- package/dist/dashboard-story.js.map +1 -0
- package/dist/domain-context.d.ts.map +1 -1
- package/dist/domain-context.js +15 -7
- package/dist/domain-context.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/kg/build.d.ts.map +1 -1
- package/dist/kg/build.js +24 -0
- package/dist/kg/build.js.map +1 -1
- package/dist/kg/types.d.ts +1 -1
- package/dist/kg/types.d.ts.map +1 -1
- package/dist/metadata/catalog.d.ts +3 -0
- package/dist/metadata/catalog.d.ts.map +1 -1
- package/dist/metadata/catalog.js +55 -6
- package/dist/metadata/catalog.js.map +1 -1
- package/dist/skills/loader.d.ts +9 -0
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +26 -2
- package/dist/skills/loader.js.map +1 -1
- package/package.json +4 -4
package/dist/app-builder.d.ts
CHANGED
|
@@ -1,8 +1,31 @@
|
|
|
1
|
-
import { type AppDocument, type DashboardDocument, type DashboardTileFilterBinding, type DashboardTileParameterBinding, type DashboardTileSourceEvidence, type DashboardVizConfig } from "@duckcodeailabs/dql-core";
|
|
1
|
+
import { type AppDocument, type DashboardDocument, type DashboardSemanticQueryRef, type DashboardStoryEvidencePlan, type DashboardTileFilterBinding, type DashboardTileParameterBinding, type DashboardTileSourceEvidence, type DashboardVizConfig } from "@duckcodeailabs/dql-core";
|
|
2
2
|
import type { KGStore } from "./kg/sqlite-fts.js";
|
|
3
3
|
import type { NarrateResult } from "./narrate.js";
|
|
4
|
+
export type AppMode = "personal" | "stakeholder";
|
|
5
|
+
export interface AppRequirement {
|
|
6
|
+
id: string;
|
|
7
|
+
question: string;
|
|
8
|
+
role: "kpi" | "trend" | "breakdown" | "detail";
|
|
9
|
+
measures: string[];
|
|
10
|
+
dimensions: string[];
|
|
11
|
+
filters: string[];
|
|
12
|
+
grain?: string;
|
|
13
|
+
ranking?: {
|
|
14
|
+
direction: "top" | "bottom";
|
|
15
|
+
limit: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export interface RequirementCoverage {
|
|
19
|
+
requirementId: string;
|
|
20
|
+
status: "covered" | "gap";
|
|
21
|
+
source: "certified_block" | "semantic_query" | "gap";
|
|
22
|
+
tileId?: string;
|
|
23
|
+
sourceId?: string;
|
|
24
|
+
trustState: AppPlanTrustState;
|
|
25
|
+
reasons: string[];
|
|
26
|
+
}
|
|
4
27
|
export type AppBuilderSkillId = "interpret_business_intent" | "match_certified_context" | "shape_business_story" | "design_dashboard_layout" | "draft_missing_sections" | "route_review";
|
|
5
|
-
export type AppPlanTileKind = "certified_block" | "draft_placeholder" | "narrative";
|
|
28
|
+
export type AppPlanTileKind = "certified_block" | "semantic_query" | "draft_placeholder" | "narrative";
|
|
6
29
|
export type AppPlanAnalysisIntent = "executive_summary" | "metric_monitoring" | "driver_analysis" | "entity_drilldown" | "trust_review" | "data_quality" | "experiment_readout";
|
|
7
30
|
export type AppPlanTileRole = "business_summary" | "kpi" | "trend" | "breakdown" | "evidence" | "trust" | "research" | "narrative";
|
|
8
31
|
export type AppPlanTrustState = "certified" | "review_required" | "draft_ready";
|
|
@@ -46,6 +69,8 @@ export interface AppPlanTile {
|
|
|
46
69
|
kind: AppPlanTileKind;
|
|
47
70
|
description?: string;
|
|
48
71
|
blockId?: string;
|
|
72
|
+
semantic?: DashboardSemanticQueryRef;
|
|
73
|
+
requirementIds?: string[];
|
|
49
74
|
sourceNodeId?: string;
|
|
50
75
|
viz: DashboardVizConfig["type"];
|
|
51
76
|
certification: "certified" | "uncertified";
|
|
@@ -98,7 +123,12 @@ export interface AppBuilderSkill {
|
|
|
98
123
|
description: string;
|
|
99
124
|
}
|
|
100
125
|
export interface AppPlan {
|
|
101
|
-
version:
|
|
126
|
+
version: 2;
|
|
127
|
+
mode: AppMode;
|
|
128
|
+
snapshotId?: string;
|
|
129
|
+
requirements: AppRequirement[];
|
|
130
|
+
requirementCoverage: RequirementCoverage[];
|
|
131
|
+
storyEvidencePlan: DashboardStoryEvidencePlan;
|
|
102
132
|
appId: string;
|
|
103
133
|
name: string;
|
|
104
134
|
prompt: string;
|
|
@@ -158,6 +188,9 @@ export interface PlanAppFromPromptInput {
|
|
|
158
188
|
preferredBlockIds?: string[];
|
|
159
189
|
maxCertifiedTiles?: number;
|
|
160
190
|
plannerMode?: "deterministic" | "ai_assisted";
|
|
191
|
+
mode?: AppMode;
|
|
192
|
+
snapshotId?: string;
|
|
193
|
+
allowSemanticQueries?: boolean;
|
|
161
194
|
}
|
|
162
195
|
export interface AppPlanValidationIssue {
|
|
163
196
|
level: "error" | "warning";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-builder.d.ts","sourceRoot":"","sources":["../src/app-builder.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"app-builder.d.ts","sourceRoot":"","sources":["../src/app-builder.ts"],"names":[],"mappings":"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"}
|
package/dist/app-builder.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { join, relative } from "node:path";
|
|
3
4
|
import { parseAppDocument, parseDashboardDocument, suggestAppId, } from "@duckcodeailabs/dql-core";
|
|
5
|
+
import { buildAnalysisQuestionPlan } from "./metadata/analysis-planner.js";
|
|
6
|
+
import { certifiedFitAllowsTier1, evaluateCertifiedBlockFit } from "./metadata/block-fit.js";
|
|
4
7
|
export const APP_BUILDER_SKILLS = [
|
|
5
8
|
{
|
|
6
9
|
id: "interpret_business_intent",
|
|
@@ -33,6 +36,163 @@ export const APP_BUILDER_SKILLS = [
|
|
|
33
36
|
description: "Keep generated analysis review-required and attach concrete next steps before stakeholder use.",
|
|
34
37
|
},
|
|
35
38
|
];
|
|
39
|
+
function appRequirementsForPrompt(prompt) {
|
|
40
|
+
const analysis = buildAnalysisQuestionPlan(prompt);
|
|
41
|
+
const measures = analysis.requestedShape.measures.length
|
|
42
|
+
? analysis.requestedShape.measures
|
|
43
|
+
: analysis.metricTerms;
|
|
44
|
+
const dimensions = analysis.requestedShape.dimensions;
|
|
45
|
+
const filters = analysis.requestedShape.filters;
|
|
46
|
+
const ranking = analysis.requestedShape.topN
|
|
47
|
+
? { direction: analysis.requestedShape.rankingDirection ?? "top", limit: analysis.requestedShape.topN.n }
|
|
48
|
+
: undefined;
|
|
49
|
+
const requirements = [{
|
|
50
|
+
id: "primary",
|
|
51
|
+
question: prompt,
|
|
52
|
+
role: ranking ? "detail" : dimensions.length ? "breakdown" : "kpi",
|
|
53
|
+
measures,
|
|
54
|
+
dimensions,
|
|
55
|
+
filters,
|
|
56
|
+
grain: analysis.requestedShape.grain,
|
|
57
|
+
...(ranking ? { ranking } : {}),
|
|
58
|
+
}];
|
|
59
|
+
const wantsTrend = analysis.mode === "trend" || /\btrend|over time|weekly|monthly|quarterly\b/i.test(prompt);
|
|
60
|
+
if (wantsTrend && !requirements.some((requirement) => requirement.role === "trend")) {
|
|
61
|
+
requirements.push({
|
|
62
|
+
id: "trend",
|
|
63
|
+
question: `${measures.join(" and ") || "primary metric"} over time`,
|
|
64
|
+
role: "trend",
|
|
65
|
+
measures,
|
|
66
|
+
dimensions: analysis.timeTerms.length ? analysis.timeTerms : ["time"],
|
|
67
|
+
filters,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
for (const dimension of dimensions.slice(0, 2)) {
|
|
71
|
+
if (requirements[0]?.dimensions.includes(dimension) && dimensions.length === 1)
|
|
72
|
+
continue;
|
|
73
|
+
requirements.push({
|
|
74
|
+
id: `by-${slugify(dimension)}`,
|
|
75
|
+
question: `${measures.join(" and ") || "primary metric"} by ${dimension}`,
|
|
76
|
+
role: "breakdown",
|
|
77
|
+
measures,
|
|
78
|
+
dimensions: [dimension],
|
|
79
|
+
filters,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return requirements;
|
|
83
|
+
}
|
|
84
|
+
function certifiedCoverageForRequirement(requirement, nodes) {
|
|
85
|
+
const plan = buildAnalysisQuestionPlan(requirement.question);
|
|
86
|
+
const ranked = nodes.map((node) => ({
|
|
87
|
+
node,
|
|
88
|
+
fit: evaluateCertifiedBlockFit({ question: requirement.question, plan, block: node }),
|
|
89
|
+
requirementFit: certifiedNodeCoversRequirement(node, requirement),
|
|
90
|
+
}));
|
|
91
|
+
const exact = ranked.find((candidate) => certifiedFitAllowsTier1(candidate.fit) && candidate.requirementFit.ok);
|
|
92
|
+
if (exact)
|
|
93
|
+
return { node: exact.node, reasons: exact.fit.reasons };
|
|
94
|
+
return {
|
|
95
|
+
reasons: Array.from(new Set(ranked.flatMap((candidate) => [
|
|
96
|
+
...(certifiedFitAllowsTier1(candidate.fit) && !candidate.requirementFit.ok ? [] : candidate.fit.reasons),
|
|
97
|
+
...candidate.requirementFit.reasons,
|
|
98
|
+
]))).slice(0, 5),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* App requirements are already structured, so they are a stronger contract than
|
|
103
|
+
* reparsing the short requirement question. This guard prevents descriptive
|
|
104
|
+
* metadata from making (for example) a product ranking look like a time trend,
|
|
105
|
+
* or an all-customer lifetime ranking look like a beverage-filtered ranking.
|
|
106
|
+
*/
|
|
107
|
+
function certifiedNodeCoversRequirement(node, requirement) {
|
|
108
|
+
const haystack = canonicalRequirementText(JSON.stringify(node));
|
|
109
|
+
const missingDimensions = requirement.dimensions.filter((dimension) => !requirementTermAliases(dimension).some((alias) => haystack.includes(` ${alias} `)));
|
|
110
|
+
const filterTerms = requirement.filters.flatMap((filter) => canonicalRequirementText(filter).trim().split(/\s+/).filter((term) => term.length > 2 && !REQUIREMENT_FILTER_STOP_WORDS.has(term)));
|
|
111
|
+
const missingFilters = filterTerms.filter((term) => !requirementTermAliases(term).some((alias) => haystack.includes(` ${alias} `)));
|
|
112
|
+
const reasons = [
|
|
113
|
+
missingDimensions.length ? `certified source does not prove dimensions: ${missingDimensions.join(", ")}` : "",
|
|
114
|
+
missingFilters.length ? `certified source does not prove filters: ${missingFilters.join(", ")}` : "",
|
|
115
|
+
].filter(Boolean);
|
|
116
|
+
return { ok: reasons.length === 0, reasons };
|
|
117
|
+
}
|
|
118
|
+
const REQUIREMENT_FILTER_STOP_WORDS = new Set([
|
|
119
|
+
"and", "for", "from", "with", "different", "type", "types", "specific", "only", "all",
|
|
120
|
+
]);
|
|
121
|
+
function canonicalRequirementText(value) {
|
|
122
|
+
return ` ${value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim()} `;
|
|
123
|
+
}
|
|
124
|
+
function requirementTermAliases(value) {
|
|
125
|
+
const term = canonicalRequirementText(value).trim();
|
|
126
|
+
if (["time", "date"].includes(term))
|
|
127
|
+
return ["time", "date", "day", "week", "month", "quarter", "year", "ordered at"];
|
|
128
|
+
if (["beverage", "beverages"].includes(term))
|
|
129
|
+
return ["beverage", "beverages", "drink", "drinks"];
|
|
130
|
+
if (["customer", "customers"].includes(term))
|
|
131
|
+
return ["customer", "customers", "customer name", "customer id"];
|
|
132
|
+
if (["product", "products"].includes(term))
|
|
133
|
+
return ["product", "products", "product name", "product id", "item", "sku"];
|
|
134
|
+
return [term];
|
|
135
|
+
}
|
|
136
|
+
function semanticTileForRequirement(kg, requirement, domain, snapshotId) {
|
|
137
|
+
if (requirement.measures.length === 0)
|
|
138
|
+
return null;
|
|
139
|
+
const hits = kg.search({
|
|
140
|
+
query: requirement.question,
|
|
141
|
+
domain: domain === "general" ? undefined : domain,
|
|
142
|
+
kinds: ["metric", "measure", "semantic_model", "dimension"],
|
|
143
|
+
limit: 24,
|
|
144
|
+
}).map((hit) => hit.node);
|
|
145
|
+
const metricNodes = hits.filter((node) => node.kind === "metric" || node.kind === "measure");
|
|
146
|
+
const modelNodes = hits.filter((node) => node.kind === "semantic_model");
|
|
147
|
+
if (metricNodes.length === 0 || modelNodes.length === 0)
|
|
148
|
+
return null;
|
|
149
|
+
const requested = new Set(requirement.measures.map((measure) => slugify(measure).replace(/-/g, "_")));
|
|
150
|
+
const metric = metricNodes.find((node) => requested.has(slugify(node.name).replace(/-/g, "_"))) ?? metricNodes[0];
|
|
151
|
+
if (!metric)
|
|
152
|
+
return null;
|
|
153
|
+
const semanticModelRefs = Array.from(new Set(modelNodes.map((node) => node.name))).slice(0, 4);
|
|
154
|
+
const id = `semantic-${requirement.id}`;
|
|
155
|
+
const fingerprintPayload = JSON.stringify({
|
|
156
|
+
metric: metric.nodeId,
|
|
157
|
+
dimensions: requirement.dimensions,
|
|
158
|
+
filters: requirement.filters,
|
|
159
|
+
models: semanticModelRefs,
|
|
160
|
+
snapshotId,
|
|
161
|
+
});
|
|
162
|
+
const semantic = {
|
|
163
|
+
id,
|
|
164
|
+
provider: metric.provenance?.toLowerCase().includes("metricflow") ? "metricflow" : "native",
|
|
165
|
+
metrics: [metric.name],
|
|
166
|
+
...(requirement.dimensions.length ? { dimensions: requirement.dimensions } : {}),
|
|
167
|
+
...(requirement.ranking ? {
|
|
168
|
+
orderBy: [{ field: metric.name, direction: requirement.ranking.direction === "bottom" ? "asc" : "desc" }],
|
|
169
|
+
limit: requirement.ranking.limit,
|
|
170
|
+
} : {}),
|
|
171
|
+
semanticModelRefs,
|
|
172
|
+
definitionFingerprint: `sha256:${createHash("sha256").update(fingerprintPayload).digest("hex")}`,
|
|
173
|
+
...(snapshotId ? { snapshotId } : {}),
|
|
174
|
+
};
|
|
175
|
+
const viz = requirement.role === "kpi"
|
|
176
|
+
? "single_value"
|
|
177
|
+
: requirement.role === "trend" ? "line" : requirement.ranking ? "bar" : "table";
|
|
178
|
+
return {
|
|
179
|
+
id,
|
|
180
|
+
title: requirement.question,
|
|
181
|
+
kind: "semantic_query",
|
|
182
|
+
semantic,
|
|
183
|
+
requirementIds: [requirement.id],
|
|
184
|
+
viz,
|
|
185
|
+
certification: "uncertified",
|
|
186
|
+
reviewStatus: "review_required",
|
|
187
|
+
trustState: "review_required",
|
|
188
|
+
rationale: "Governed semantic coverage for a requirement not fully covered by a compatible certified block.",
|
|
189
|
+
sourceEvidence: [
|
|
190
|
+
{ source: metric.nodeId, nodeId: metric.nodeId, kind: metric.kind, reason: "Reviewed semantic metric", trustState: "review_required" },
|
|
191
|
+
...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" })),
|
|
192
|
+
],
|
|
193
|
+
reviewTasks: ["Review semantic definition freshness and result shape before stakeholder publication."],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
36
196
|
export function planAppFromPrompt(input) {
|
|
37
197
|
const prompt = input.prompt.trim();
|
|
38
198
|
if (!prompt)
|
|
@@ -54,7 +214,61 @@ export function planAppFromPrompt(input) {
|
|
|
54
214
|
const promptFilters = inferFilters(prompt);
|
|
55
215
|
const blockFilters = filtersFromCertifiedNodes(certifiedNodes);
|
|
56
216
|
const filters = blockFilters.length > 0 ? mergeAppFilters(blockFilters, promptFilters) : promptFilters;
|
|
57
|
-
const
|
|
217
|
+
const candidateCertifiedTiles = certifiedNodes.map((node, index) => tileFromCertifiedNode(node, index, analysisIntent, filters));
|
|
218
|
+
const requirements = appRequirementsForPrompt(prompt);
|
|
219
|
+
const coverage = [];
|
|
220
|
+
const selectedCertifiedIds = new Set();
|
|
221
|
+
const semanticTiles = [];
|
|
222
|
+
for (const requirement of requirements) {
|
|
223
|
+
const match = certifiedCoverageForRequirement(requirement, certifiedNodes);
|
|
224
|
+
if (match.node) {
|
|
225
|
+
const tileId = slugify(match.node.name) || match.node.nodeId;
|
|
226
|
+
selectedCertifiedIds.add(match.node.nodeId);
|
|
227
|
+
coverage.push({
|
|
228
|
+
requirementId: requirement.id,
|
|
229
|
+
status: "covered",
|
|
230
|
+
source: "certified_block",
|
|
231
|
+
tileId,
|
|
232
|
+
sourceId: match.node.nodeId,
|
|
233
|
+
trustState: "certified",
|
|
234
|
+
reasons: match.reasons,
|
|
235
|
+
});
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const semanticTile = input.allowSemanticQueries === false
|
|
239
|
+
? null
|
|
240
|
+
: semanticTileForRequirement(input.kg, requirement, domain, input.snapshotId);
|
|
241
|
+
if (semanticTile) {
|
|
242
|
+
semanticTiles.push(semanticTile);
|
|
243
|
+
coverage.push({
|
|
244
|
+
requirementId: requirement.id,
|
|
245
|
+
status: "covered",
|
|
246
|
+
source: "semantic_query",
|
|
247
|
+
tileId: semanticTile.id,
|
|
248
|
+
sourceId: semanticTile.semantic?.id,
|
|
249
|
+
trustState: "review_required",
|
|
250
|
+
reasons: ["No fully compatible certified block; covered by a governed semantic query."],
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
coverage.push({
|
|
255
|
+
requirementId: requirement.id,
|
|
256
|
+
status: "gap",
|
|
257
|
+
source: "gap",
|
|
258
|
+
trustState: "draft_ready",
|
|
259
|
+
reasons: match.reasons.length ? match.reasons : ["No compatible certified block or reviewed semantic definition was found."],
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Keep compatible certified context only. For legacy unshaped prompts, retain
|
|
264
|
+
// the best certified candidates so existing simple App generation stays useful.
|
|
265
|
+
// Requirement coverage is strict, while explicitly discovered certified
|
|
266
|
+
// context may still appear as supporting evidence tiles. It never closes a
|
|
267
|
+
// requirement unless the compatibility check above succeeds.
|
|
268
|
+
// Compatible blocks close requirements; other strongly related certified
|
|
269
|
+
// blocks may still appear as supporting context, but never change coverage.
|
|
270
|
+
const certifiedTiles = candidateCertifiedTiles;
|
|
271
|
+
const dashboardTiles = [...certifiedTiles, ...semanticTiles];
|
|
58
272
|
const scopedReports = inferScopedReports(prompt, domain, analysisIntent, certifiedNodes);
|
|
59
273
|
const certifiedContextSummary = summarizeCertifiedContext([
|
|
60
274
|
...certifiedNodes,
|
|
@@ -76,7 +290,18 @@ export function planAppFromPrompt(input) {
|
|
|
76
290
|
filterLabels: filters.map((filter) => filter.label),
|
|
77
291
|
});
|
|
78
292
|
return {
|
|
79
|
-
version:
|
|
293
|
+
version: 2,
|
|
294
|
+
mode: input.mode ?? "stakeholder",
|
|
295
|
+
...(input.snapshotId ? { snapshotId: input.snapshotId } : {}),
|
|
296
|
+
requirements,
|
|
297
|
+
requirementCoverage: coverage,
|
|
298
|
+
storyEvidencePlan: {
|
|
299
|
+
version: 1,
|
|
300
|
+
goal: prompt,
|
|
301
|
+
audience,
|
|
302
|
+
eligibleTileIds: dashboardTiles.map((tile) => tile.id),
|
|
303
|
+
driverTileIds: dashboardTiles.filter((tile) => tile.display?.role === "breakdown" || tile.requirementIds?.some((id) => id.startsWith("by-"))).map((tile) => tile.id),
|
|
304
|
+
},
|
|
80
305
|
appId,
|
|
81
306
|
name: appName,
|
|
82
307
|
prompt,
|
|
@@ -139,7 +364,7 @@ export function planAppFromPrompt(input) {
|
|
|
139
364
|
title: inferDashboardTitle(prompt, domain, appName),
|
|
140
365
|
description: `Certified app surface for ${audience}. Draft gaps stay in reports until reviewed.`,
|
|
141
366
|
filters,
|
|
142
|
-
tiles:
|
|
367
|
+
tiles: dashboardTiles,
|
|
143
368
|
},
|
|
144
369
|
],
|
|
145
370
|
caveats: [
|
|
@@ -154,9 +379,9 @@ export function planAppFromPrompt(input) {
|
|
|
154
379
|
],
|
|
155
380
|
coverage: {
|
|
156
381
|
certifiedTiles: certifiedTiles.length,
|
|
157
|
-
gaps:
|
|
158
|
-
ratio:
|
|
159
|
-
?
|
|
382
|
+
gaps: coverage.filter((item) => item.status === "gap").length,
|
|
383
|
+
ratio: coverage.length > 0
|
|
384
|
+
? coverage.filter((item) => item.status === "covered").length / coverage.length
|
|
160
385
|
: 0,
|
|
161
386
|
},
|
|
162
387
|
};
|
|
@@ -165,7 +390,7 @@ export function validateAppPlan(plan, kg) {
|
|
|
165
390
|
const issues = [];
|
|
166
391
|
let certifiedTiles = 0;
|
|
167
392
|
let draftTiles = 0;
|
|
168
|
-
if (plan.version !==
|
|
393
|
+
if (plan.version !== 2)
|
|
169
394
|
issues.push(error("version", "unsupported app plan version"));
|
|
170
395
|
if (!plan.appId || !/^[a-z0-9][a-z0-9_-]*$/i.test(plan.appId)) {
|
|
171
396
|
issues.push(error("appId", "appId must be folder-safe"));
|
|
@@ -207,6 +432,23 @@ export function validateAppPlan(plan, kg) {
|
|
|
207
432
|
issues.push(error(path, "certified block tiles must be visibly certified"));
|
|
208
433
|
}
|
|
209
434
|
}
|
|
435
|
+
else if (tile.kind === "semantic_query") {
|
|
436
|
+
draftTiles += 1;
|
|
437
|
+
if (!tile.semantic) {
|
|
438
|
+
issues.push(error(`${path}.semantic`, "semantic query tile requires a canonical semantic reference"));
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
if (tile.semantic.metrics.length === 0)
|
|
442
|
+
issues.push(error(`${path}.semantic.metrics`, "semantic query requires a metric"));
|
|
443
|
+
if (tile.semantic.semanticModelRefs.length === 0)
|
|
444
|
+
issues.push(error(`${path}.semantic.semanticModelRefs`, "semantic query requires reviewed model references"));
|
|
445
|
+
if (!tile.semantic.definitionFingerprint)
|
|
446
|
+
issues.push(error(`${path}.semantic.definitionFingerprint`, "semantic query requires a definition fingerprint"));
|
|
447
|
+
}
|
|
448
|
+
if (tile.certification === "certified" || tile.reviewStatus === "certified") {
|
|
449
|
+
issues.push(error(path, "semantic query tiles cannot be presented as certified blocks"));
|
|
450
|
+
}
|
|
451
|
+
}
|
|
210
452
|
else {
|
|
211
453
|
draftTiles += 1;
|
|
212
454
|
issues.push(error(path, "stakeholder dashboard tiles must be certified blocks; route generated story, trust, and analysis work through scoped reports or Copilot investigations"));
|
|
@@ -240,6 +482,15 @@ export function generateAppFromPlan(projectRoot, plan, kg, options = {}) {
|
|
|
240
482
|
throw new Error(`App already exists: ${relative(projectRoot, appDir)}`);
|
|
241
483
|
}
|
|
242
484
|
const dashboardId = plan.pages[0]?.id || "overview";
|
|
485
|
+
const sourceNodes = plan.pages.flatMap((page) => page.tiles)
|
|
486
|
+
.flatMap((tile) => tile.sourceEvidence ?? [])
|
|
487
|
+
.map((evidence) => evidence.nodeId ? kg.getNode(evidence.nodeId) : undefined)
|
|
488
|
+
.filter((node) => Boolean(node));
|
|
489
|
+
const usedDomains = Array.from(new Set([plan.domain, ...sourceNodes.map((node) => node.domain).filter((domain) => Boolean(domain))]));
|
|
490
|
+
const requiredExports = Array.from(new Set(sourceNodes.flatMap((node) => {
|
|
491
|
+
const value = node.payload?.requiredExports;
|
|
492
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
493
|
+
})));
|
|
243
494
|
const app = {
|
|
244
495
|
version: 1,
|
|
245
496
|
id: plan.appId,
|
|
@@ -253,10 +504,10 @@ export function generateAppFromPlan(projectRoot, plan, kg, options = {}) {
|
|
|
253
504
|
"Certified tiles must reference existing certified blocks.",
|
|
254
505
|
],
|
|
255
506
|
caveats: plan.caveats,
|
|
256
|
-
visibility: "shared",
|
|
507
|
+
visibility: plan.mode === "personal" ? "private" : "shared",
|
|
257
508
|
ownerDomain: plan.domain,
|
|
258
|
-
usesDomains:
|
|
259
|
-
requiredExports
|
|
509
|
+
usesDomains: usedDomains,
|
|
510
|
+
requiredExports,
|
|
260
511
|
domain: plan.domain,
|
|
261
512
|
audience: plan.audience,
|
|
262
513
|
lifecycle: plan.lifecycle,
|
|
@@ -314,16 +565,16 @@ export function generateAppFromPlan(projectRoot, plan, kg, options = {}) {
|
|
|
314
565
|
const dashboards = plan.pages.map((page, pageIndex) => ({
|
|
315
566
|
version: 1,
|
|
316
567
|
id: page.id,
|
|
317
|
-
// Story layout on the primary page when a narration was supplied.
|
|
318
568
|
...(options.narration && pageIndex === 0
|
|
319
569
|
? { sections: buildStorySections(page.tiles, options.narration) }
|
|
320
570
|
: {}),
|
|
571
|
+
...(pageIndex === 0 ? { story: plan.storyEvidencePlan } : {}),
|
|
321
572
|
metadata: {
|
|
322
573
|
title: page.title,
|
|
323
574
|
description: page.description ?? plan.planning.displayStrategy,
|
|
324
575
|
domain: plan.domain,
|
|
325
576
|
audience: plan.audience,
|
|
326
|
-
visibility: "shared",
|
|
577
|
+
visibility: plan.mode === "personal" ? "private" : "shared",
|
|
327
578
|
lifecycle: "draft",
|
|
328
579
|
tags: plan.tags,
|
|
329
580
|
businessOutcome: plan.planning.normalizedGoal,
|
|
@@ -1168,9 +1419,12 @@ function buildLayoutItems(tiles) {
|
|
|
1168
1419
|
trustState: tile.trustState ?? tile.display?.trustState ?? (tile.certification === "certified" ? "certified" : "draft_ready"),
|
|
1169
1420
|
reviewStatus: tile.reviewStatus,
|
|
1170
1421
|
};
|
|
1171
|
-
if (
|
|
1422
|
+
if (tile.kind === "certified_block" && tile.blockId) {
|
|
1172
1423
|
item.block = { blockId: tile.blockId };
|
|
1173
1424
|
}
|
|
1425
|
+
else if (tile.kind === "semantic_query" && tile.semantic) {
|
|
1426
|
+
item.semantic = tile.semantic;
|
|
1427
|
+
}
|
|
1174
1428
|
else {
|
|
1175
1429
|
item.text = { markdown: markdownForGeneratedPlanTile(tile) };
|
|
1176
1430
|
}
|
|
@@ -1200,7 +1454,8 @@ function markdownForGeneratedPlanTile(tile) {
|
|
|
1200
1454
|
].filter(Boolean).join("\n");
|
|
1201
1455
|
}
|
|
1202
1456
|
function isDashboardTile(tile) {
|
|
1203
|
-
return tile.kind === "certified_block" && tile.certification === "certified" && Boolean(tile.blockId)
|
|
1457
|
+
return (tile.kind === "certified_block" && tile.certification === "certified" && Boolean(tile.blockId))
|
|
1458
|
+
|| (tile.kind === "semantic_query" && Boolean(tile.semantic));
|
|
1204
1459
|
}
|
|
1205
1460
|
function appPlanReadme(plan, validation) {
|
|
1206
1461
|
const reviewDashboardItems = plan.pages.flatMap((page) => page.tiles
|